java.lang.reflect包的淺析

YZcxy發表於2017-12-05

Object(源頭)

Java的一切都是物件

Object是反射的源頭

Class是反射的導演

Class(導演)

在Java程式執行時,JVM會對所有的物件維護一個獨一無二的型別標識,這就是Class物件。

Java的基本型別和關鍵字void也對應一個Class物件。
相同元素型別和維數的陣列也對應一個Class物件。

獲取Class物件的幾種方法:

  • 一個物件通過.class
  • 一個物件通過getClass方法
  • Class.forName(String)

Class物件有一些重要的方法:

  • getConstructor系列,用於獲取構造方法
  • getField系列,用於獲取屬性
  • getMethod系列,用於獲取方法

上述系列中包含Declared系列,可以獲取當前物件的所有型別的反射(包含private),但是不能獲取父類的反射

Constructor,Field,Method都是Class導演的三大利器,它們都位於java.lang.reflect包下,接下來將分別對這三大利器進行展示。

Constructor

通過Class物件獲取到的Constructor物件之後,最常用的操作就是用來例項化物件,呼叫newInstance方法即可。

其實在Class物件中也有一個newInstance方法,也可以用來例項化物件,它們的區別是什麼呢?

  • Class.newInstance() 只能夠呼叫無參的建構函式,即預設的建構函式,要求被呼叫的建構函式是可見的,也即必須是public型別的;
  • Constructor.newInstance() 可以根據傳入的引數,呼叫任意構造建構函式,在特定的情況下,可以呼叫私有的建構函式。

Demo

//Class.newInstance()
//只能呼叫public屬性的無參建構函式  
A a = (A)Class.forName(A.class.getName()).newInstance();  

//Constructor.newInstance()
Class c= Class.forName(A.class.getName());
   
/*以下呼叫無參的、私有建構函式*/  
//獲得無參構造
Constructor c0=c.getDeclaredConstructor();  
//設定無參構造是可訪問的
c0.setAccessible(true);   
A a0=(A)c0.newInstance();
    
//呼叫無參建構函式,生成物件例項
/*以下呼叫帶參的、私有建構函式*/   
Constructor c1=c.getDeclaredConstructor(new Class[]{int.class,int.class});   
c1.setAccessible(true);   
//呼叫有參建構函式,生成物件例項 
A a1=(A)c1.newInstance(new Object[]{5,6});   

複製程式碼

使用場景:

如果使用介面模式,使用new建立一個門的物件,Door door = new WoodenDoor(),當以換成其他門,需要修改程式碼,Door door = new OtherDoor()。所以我們需要使用工廠模式,需要什麼門就生產什麼門,如果我們再使用newInstance()方法來生產,則只需要修改配置檔案即可。

Field

通過Class物件獲取到的Field物件之後,我們就可以自由的檢視和設定物件的屬性值。

關鍵方法:

  • get(Object object)檢視特定物件的屬性值
  • set(Object object, Object value)給特定物件設定屬性
  • setAccessible(boolean flag)讓private成員擁有public許可權

Demo

//獲取Class物件
Class aClass = MyObject.class
//通過Class物件獲取Field物件
Field field = aClass.getDeclaredField("someField");
//設定可訪問許可權
field.setAccessible(true);

MyObject objectInstance = new MyObject();

//獲取特定的物件的變數屬性值
Object value = field.get(objectInstance);
//給特定物件的變數設定屬性
field.set(objetInstance, value);
複製程式碼

因為Constructor,Field,Method都繼承自AccessibleObject類,所有都擁有setAccessible方法,個人感覺setAccessible有點窺探隱私的感覺,哈哈哈,不知道Class導演怎麼看。

Method

通過Class物件獲取到的Method物件之後,想都不用想啊,獲取一個方法不呼叫它幹嘛,所以我們最常用的操作應該就是invoke方法。

相信大家對invoke並不會陌生,因為很多的異常,最後都會定位到invoke方法。

java.lang.NullPointerException
  at ......
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)
複製程式碼

invoke(Object receiver, Object… args),可以分為兩種,傳遞receiver參數列示呼叫特定物件的方法,傳null表示呼叫靜態方法。

//獲取一個方法名為doSomesthing,引數型別為String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);

//呼叫靜態的doSomesthing方法,傳遞引數"parameter-value1"
Object returnValue = method.invoke(null, "parameter-value1");
複製程式碼

最後說兩句

本文只是對Clss物件以及reflect包下的物件進行簡單的使用說明,關於反射的實現和原理,還有待於深入研究。例如AccessibleObject物件以及對應相關xxxAccessor的實現。