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的實現。