一、反射機制是什么?
二、反射的具體使用
2.1 獲取對象的包名以及類名
2.2 獲取Class對象
2.3 getInstance()獲取指定類型的實例化對象
2.4 通過構造函數對象實例化對象
2.5 獲取類繼承的接口
2.6 獲取父類相關信息
2.7 獲取當前類的公有屬性和私有屬性以及更新
2.8 獲取以及調用類的公有/私有方法
三、反射的優缺點
3.1 優點
3.2 缺點
?
一、反射機制是什么?
反射是什么?什么是反?什么是正射?有反就有正,我們知道正常情況, 如果我們希望創建一個對象,會使用以下的語句:
Person person = new Person();
其實我們第一次執行上面的語句的時候,JVM會先加載Person.class
,加載到內存完之后,在方法區/堆中會創建了一個Class
對象,對應這個Person
類。這里有爭議,有人說是在方法區,有些人說是在堆。個人感覺應該JVM規范說是在方法區,但是不是強制要求,而且不同版本的JVM實現也不一樣。具體參考以下鏈接,這里不做解釋:https://www.cnblogs.com/xy-nb/p/6773051.html 而上面正常的初始化對象的方法,也可以說是“正射”,就是使用Class
對象創建出一個Person
對象。
而反射則相反,是根據Person
對象,獲取到Class
對象,然后可以獲取到Person
類的相關信息,進行初始化或者調用等一系列操作。
在運行狀態時,可以構造任何一個類的對象,獲取到任意一個對象所屬的類信息,以及這個類的成員變量或者方法,可以調用任意一個對象的屬性或者方法。可以理解為具備了動態加載對象以及對對象的基本信息進行剖析和使用的能力。
提供的功能包括:
1.在運行時判斷一個對象所屬的類
2.在運行時構造任意一個類的對象
3.在運行時獲取一個類定義的成員變量以及方法
4.在運行時調用任意一個對象的方法
5.生成動態代理
靈活,強大,可以在運行時裝配,無需在組件之間進行源代碼鏈接,但是使用不當效率會有影響。所有類的對象都是Class的實例。既然我們可以對類的全限定名,方法以及參數等進行配置,完成對象的初始化,那就是相當于增加了java的可配置性。
這里特別需要明確的一點:類本身也是一個對象,方法也是一個對象,在Java里面萬物皆可對象,除了基礎數據類型...
二、反射的具體使用
2.1 獲取對象的包名以及類名
package invocation;
public class MyInvocation {
public static void main(String[] args) {
getClassNameTest();
}
public static void getClassNameTest(){
MyInvocation myInvocation = new MyInvocation();
System.out.println("class: " + myInvocation.getClass());
System.out.println("simpleName: " + myInvocation.getClass().getSimpleName());
System.out.println("name: " + myInvocation.getClass().getName());
System.out.println("package: " +
"" + myInvocation.getClass().getPackage());
}
}
運行結果:
class: class invocation.MyInvocation
simpleName: MyInvocation
name: invocation.MyInvocation
package: package invocation
由上面結果我們可以看到:1.getClass()
:打印會帶著class+全類名 2.getClass().getSimpleName()
:只會打印出類名 3.getName()
:會打印全類名 4.getClass().getPackage()
:打印出package+包名
getClass()
獲取到的是一個對象,getPackage()
也是。
2.2 獲取Class對象
在java中,一切皆對象。java中可以分為兩種對象,實例對象和Class對象。這里我們說的獲取Class對象,其實就是第二種,Class對象代表的是每個類在運行時的類型信息,指和類相關的信息。比如有一個Student
類,我們用Student student = new Student()
new一個對象出來,這個時候Student
這個類的信息其實就是存放在一個對象中,這個對象就是Class類的對象,而student這個實例對象也會和Class對象關聯起來。我們有三種方式可以獲取一個類在運行時的Class對象,分別是
Class.forName("com.Student")
student.getClass()
Student.class
實例代碼如下:
package invocation;
public class MyInvocation {
public static void main(String[] args) {
getClassTest();
}
public static void getClassTest(){
Class<?> invocation1 = null;
Class<?> invocation2 = null;
Class<?> invocation3 = null;
try {
// 最常用的方法
invocation1 = Class.forName("invocation.MyInvocation");
}catch (Exception ex){
ex.printStackTrace();
}
invocation2 = new MyInvocation().getClass();
invocation3 = MyInvocation.class;
System.out.println(invocation1);
System.out.println(invocation2);
System.out.println(invocation3);
}
}
執行的結果如下,三個結果一樣:
class invocation.MyInvocation
class invocation.MyInvocation
class invocation.MyInvocation
2.3 getInstance()獲取指定類型的實例化對象
首先我們有一個Student類,后面都會沿用這個類,將不再重復。
class Student{
private int age;
private String name;
public Student() {
}
public Student(int age) {
this.age = age;
}
public Student(String name) {
this.name = name;
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
我們可以使用getInstance()
方法構造出一個Student的對象:
public static void getInstanceTest() {
try {
Class<?> stduentInvocation = Class.forName("invocation.Student");
Student student = (Student) stduentInvocation.newInstance();
student.setAge(9);
student.setName("Hahs");
System.out.println(student);
}catch (Exception ex){
ex.printStackTrace();
}
}
輸出結果如下:
Student{age=9, name='Hahs'}
但是如果我們取消不寫Student的無參構造方法呢?就會出現下面的報錯:
java.lang.InstantiationException: invocation.Student
at java.lang.Class.newInstance(Class.java:427)
at invocation.MyInvocation.getInstanceTest(MyInvocation.java:40)
at invocation.MyInvocation.main(MyInvocation.java:8)
Caused by: java.lang.NoSuchMethodException: invocation.Student.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 2 more
這是因為我們重寫了構造方法,而且是有參構造方法,如果不寫構造方法,那么每個類都會默認有無參構造方法,重寫了就不會有無參構造方法了,所以我們調用newInstance()
的時候,會報沒有這個方法的錯誤。值得注意的是,newInstance()
是一個無參構造方法。
2.4 通過構造函數對象實例化對象
除了newInstance()
方法之外,其實我們還可以通過構造函數對象獲取實例化對象,怎么理解?這里只構造函數對象,而不是構造函數,也就是構造函數其實就是一個對象,我們先獲取構造函數對象,當然也可以使用來實例化對象。
可以先獲取一個類的所有的構造方法,然后遍歷輸出:
public static void testConstruct(){
try {
Class<?> stduentInvocation = Class.forName("invocation.Student");
Constructor<?> cons[] = stduentInvocation.getConstructors();
for(int i=0;i<cons.length;i++){
System.out.println(cons[i]);
}
}catch (Exception ex){
ex.printStackTrace();
}
}
輸出結果:
public invocation.Student(int,java.lang.String)
public invocation.Student(java.lang.String)
public invocation.Student(int)
public invocation.Student()
取出一個構造函數我們可以獲取到它的各種信息,包括參數,參數個數,類型等等:
public static void constructGetInstance() {
try {
Class<?> stduentInvocation = Class.forName("invocation.Student");
Constructor<?> cons[] = stduentInvocation.getConstructors();
Constructor constructors = cons[0];
System.out.println("name: " + constructors.getName());
System.out.println("modifier: " + constructors.getModifiers());
System.out.println("parameterCount: " + constructors.getParameterCount());
System.out.println("構造參數類型如下:");
for (int i = 0; i < constructors.getParameterTypes().length; i++) {
System.out.println(constructors.getParameterTypes()[i].getName());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
輸出結果,modifier
是權限修飾符,1表示為public
,我們可以知道獲取到的構造函數是兩個參數的,第一個是int,第二個是String類型,看來獲取出來的順序并不一定是我們書寫代碼的順序。
name: invocation.Student
modifier: 1
parameterCount: 2
構造參數類型如下:
int
java.lang.String
既然我們可以獲取到構造方法這個對象了,那么我們可不可以通過它去構造一個對象呢?答案肯定是可以!!!下面我們用不同的構造函數來創建對象:
public static void constructGetInstanceTest() {
try {
Class<?> stduentInvocation = Class.forName("invocation.Student");
Constructor<?> cons[] = stduentInvocation.getConstructors();
// 一共定義了4個構造器
Student student1 = (Student) cons[0].newInstance(9,"Sam");
Student student2 = (Student) cons[1].newInstance("Sam");
Student student3 = (Student) cons[2].newInstance(9);
Student student4 = (Student) cons[3].newInstance();
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
System.out.println(student4);
} catch (Exception ex) {
ex.printStackTrace();
}
輸出如下:
Student{age=9, name='Sam'}
Student{age=0, name='Sam'}
Student{age=9, name='null'}
Student{age=0, name='null'}
構造器的順序我們是必須一一針對的,要不會報一下的參數不匹配的錯誤:
java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at invocation.MyInvocation.constructGetInstanceTest(MyInvocation.java:85)
at invocation.MyInvocation.main(MyInvocation.java:8)
2.5 獲取類繼承的接口
通過反射我們可以獲取接口的方法,如果我們知道某個類實現了接口的方法,同樣可以做到通過類名創建對象調用到接口的方法。
首先我們定義兩個接口,一個InSchool
:
public interface InSchool {
public void attendClasses();
}
一個AtHome
:
public interface AtHome {
public void doHomeWork();
}
創建一個實現兩個接口的類Student.java
public class Student implements AtHome, InSchool {
public void doHomeWork() {
System.out.println("I am a student,I am doing homework at home");
}
public void attendClasses() {
System.out.println("I am a student,I am attend class in school");
}
}
測試代碼如下:
public class Test {
public static void main(String[] args) throws Exception {
Class<?> studentClass = Class.forName("invocation.Student");
Class<?>[] interfaces = studentClass.getInterfaces();
for (Class c : interfaces) {
// 獲取接口
System.out.println(c);
// 獲取接口里面的方法
Method[] methods = c.getMethods();
// 遍歷接口的方法
for (Method method : methods) {
// 通過反射創建對象
Student student = (Student) studentClass.newInstance();
// 通過反射調用方法
method.invoke(student, null);
}
}
}
}
?
可以看出其實我們可以獲取到接口的數組,并且里面的順序是我們繼承的順序,通過接口的Class對象,我們可以獲取到接口的方法,然后通過方法反射調用實現類的方法,因為這是一個無參數的方法,所以只需要傳null即可。
2.6 獲取父類相關信息
主要是使用getSuperclass()
方法獲取父類,當然也可以獲取父類的方法,執行父類的方法,首先創建一個Animal.java
:
public class Animal {
public void doSomething(){
System.out.println("animal do something");
}
}
Dog.java
繼承于Animal.java
:
public class Dog extends Animal{
public void doSomething(){
System.out.println("Dog do something");
}
}
我們可以通過反射創建Dog
對象,獲取其父類Animal
以及創建對象,當然也可以獲取Animal
的默認父類Object
:
public class Test {
public static void main(String[] args) throws Exception {
Class<?> dogClass = Class.forName("invocation02.Dog");
System.out.println(dogClass);
invoke(dogClass);
Class<?> animalClass = dogClass.getSuperclass();
System.out.println(animalClass);
invoke(animalClass);
Class<?> objectClass = animalClass.getSuperclass();
System.out.println(objectClass);
invoke(objectClass);
}
public static void invoke(Class<?> myClass) throws Exception {
Method[] methods = myClass.getMethods();
// 遍歷接口的方法
for (Method method : methods) {
if (method.getName().equalsIgnoreCase("doSomething")) {
// 通過反射調用方法
method.invoke(myClass.newInstance(), null);
}
}
}
}
?
2.7 獲取當前類的公有屬性和私有屬性以及更新
創建一個Person.java
,里面有靜態變量,非靜態變量,以及public
,protected
,private
不同修飾的屬性。
public class Person {
public static String type ;
private static String subType ;
// 名字(公開)
public String name;
protected String gender;
private String address;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
使用getFields()
可以獲取到public的屬性,包括static屬性,使用getDeclaredFields()
可以獲取所有聲明的屬性,不管是public
,protected
,private
不同修飾的屬性。
修改public
屬性,只需要field.set(object,value)
即可,但是private
屬性不能直接set,否則會報以下的錯誤。
Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.set(Field.java:761)
at invocation03.Tests.main(Tests.java:21)
那么需要怎么做呢?private默認是不允許外界操作其值的,這里我們可以使用field.setAccessible(true);
,相當于打開了操作的權限。
那static的屬性修改和非static的一樣,但是我們怎么獲取呢?如果是public
修飾的,可以直接用類名獲取到,如果是private
修飾的,那么需要使用filed.get(object)
,這個方法其實對上面說的所有的屬性都可以的。測試代碼如下
public class Tests {
public static void main(String[] args) throws Exception{
Class<?> personClass = Class.forName("invocation03.Person");
Field[] fields = personClass.getFields();
// 獲取公開的屬性
for(Field field:fields){
System.out.println(field);
}
System.out.println("=================");
// 獲取所有聲明的屬性
Field[] declaredFields = personClass.getDeclaredFields();
for(Field field:declaredFields){
System.out.println(field);
}
System.out.println("=================");
Person person = (Person) personClass.newInstance();
person.name = "Sam";
System.out.println(person);
// 修改public屬性
Field fieldName = personClass.getDeclaredField("name");
fieldName.set(person,"Jone");
// 修改private屬性
Field addressName = personClass.getDeclaredField("address");
// 需要修改權限
addressName.setAccessible(true);
addressName.set(person,"東風路47號");
System.out.println(person);
// 修改static 靜態public屬性
Field typeName = personClass.getDeclaredField("type");
typeName.set(person,"人類");
System.out.println(Person.type);
// 修改靜態 private屬性
Field subType = personClass.getDeclaredField("subType");
subType.setAccessible(true);
subType.set(person,"黃種人");
System.out.println(subType.get(person));
}
}
?
從結果可以看出,不管是public
,還是protected
,private
修飾的,我們都可以通過反射對其進行查詢和修改,不管是靜態變量還是非靜態變量。getDeclaredField()
可以獲取到所有聲明的屬性,而getFields()
則只能獲取到public
的屬性。對于非public的屬性,我們需要修改其權限才能訪問和修改:field.setAccessible(true)
。
獲取屬性值需要使用field.get(object)
,值得注意的是:每個屬性,其本身就是對象
2.8 獲取以及調用類的公有/私有方法
既然可以獲取到公有屬性和私有屬性,那么我想,執行公有方法和私有方法應該都不是什么問題??
那下面我們一起來學習一下...
先定義一個類,包含各種修飾符,以及是否包含參數,是否為靜態方法,Person.java
:
public class Person {
// 非靜態公有無參數
public void read(){
System.out.println("reading...");
}
// 非靜態公有無參數有返回
public String getName(){
return "Sam";
}
// 非靜態公有帶參數
public int readABookPercent(String name){
System.out.println("read "+name);
return 80;
}
// 私有有返回值
private String getAddress(){
return "東方路";
}
// 公有靜態無參數無返回值
public static void staticMethod(){
System.out.println("static public method");
}
// 公有靜態有參數
public static void staticMethodWithArgs(String args){
System.out.println("static public method:"+args);
}
// 私有靜態方法
private static void staticPrivateMethod(){
System.out.println("static private method");
}
}
首先我們來看看獲取里面所有的方法:
public class Tests {
public static void main(String[] args) throws Exception {
Class<?> personClass = Class.forName("invocation03.Person");
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("=============================================");
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
}
}
getMethods()
確實可以獲取所有的公有的方法,但是有一個問題,就是他會把父類的也獲取到,也就是上面圖片綠色框里面的,我們知道所有的類默認都繼承了Object
類,所以它把Object
的那些方法都獲取到了。而getDeclaredMethods
確實可以獲取到公有和私有的方法,不管是靜態還是非靜態,但是它是獲取不到父類的方法的。
那如果我們想調用方法呢?先試試調用非靜態方法:
public class Tests {
public static void main(String[] args) throws Exception {
Class<?> personClass = Class.forName("invocation03.Person");
Person person = (Person) personClass.newInstance();
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if(method.getName().equalsIgnoreCase("read")){
method.invoke(person,null);
System.out.println("===================");
}else if(method.getName().equalsIgnoreCase("getName")){
System.out.println(method.invoke(person,null));
System.out.println("===================");
}else if(method.getName().equalsIgnoreCase("readABookPercent")){
System.out.println(method.invoke(person,"Sam"));
System.out.println("===================");
}
}
}
}
結果如下,可以看出method.invoke(person,null);
是調用無參數的方法,而method.invoke(person,"Sam")
則是調用有參數的方法,要是有更多參數,也只需要在里面多加一個參數即可,返回值也同樣可以獲取到。
?
那么private
方法呢?我們照著來試試,試試就試試,who 怕 who?
public class Tests {
public static void main(String[] args) throws Exception {
Class<?> personClass = Class.forName("invocation03.Person");
Person person = (Person) personClass.newInstance();
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if(method.getName().equalsIgnoreCase("getAddress")){
method.invoke(person,null);
}
}
}
}
結果報錯了:
Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at invocation03.Tests.main(Tests.java:13)
?
一看就是沒有權限,小場面,不要慌,我來操作一波,只要加上
method.setAccessible(true);
?
那么問題來了,上面說的都是非靜態的,我就想要調用靜態的方法。當然用上面的方法,對象也可以直接調用到類的方法的:?
一點問題都沒有,為什么輸出結果有幾個null,那是因為這函數是無返回值的呀,笨蛋...
如果我不想用遍歷方法的方式,再去判斷怎么辦?能不能直接獲取到我想要的方法啊?那答案肯定是可以啊。
public class Tests {
public static void main(String[] args) throws Exception {
Class<?> personClass = Class.forName("invocation03.Person");
Person person = (Person) personClass.newInstance();
Method method = personClass.getMethod("readABookPercent", String.class);
method.invoke(person, "唐詩三百首");
}
}
結果和上面調用的完全一樣,圖我就不放了,就一行字。要是這個方法沒有參數呢?那就給一個null就可以啦。或者不給也可以。
public class Tests {
public static void main(String[] args) throws Exception {
Class<?> personClass = Class.forName("invocation03.Person");
Person person = (Person) personClass.newInstance();
Method method = personClass.getMethod("getName",null);
System.out.println(method.invoke(person));
}
}
三、反射的優缺點
3.1 優點
反射可以在不知道會運行哪一個類的情況下,獲取到類的信息,創建對象以及操作對象。這其實很方便于拓展,所以反射會是框架設計的靈魂,因為框架在設計的時候,為了降低耦合度,肯定是需要考慮拓展等功能的,不能將類型寫死,硬編碼。
降低耦合度,變得很靈活,在運行時去確定類型,綁定對象,體現了多態功能。
3.2 缺點
這么好用,沒有缺點?怎么可能!!!有利就有弊,事物都是有雙面性的。即使功能很強大,但是反射是需要動態類型的,JVM
沒有辦法優化這部分代碼,執行效率相對直接初始化對象較低。一般業務代碼不建議使用。
反射可以修改權限,比如上面訪問到private
這些方法和屬性,這是會破壞封裝性的,有安全隱患,有時候,還會破壞單例的設計。
反射會使代碼變得復雜,不容易維護,畢竟代碼還是要先寫給人看的嘛,逃~