Java的反射機制允許程式員在執行期藉助於Reflection API取得任何類的內部信息,並能操作對象的屬性和方法,在各類框架中應用非常廣泛。這一期是關於反射內容的筆記,包含Class類、Field類、Method類、Constructor類及相關方法。 ...
需求:
根據配置文件 re.properties 中指定的信息,創建對象,並調用方法
classfullpath=com.hiibird.Cat
method=hi
使用現有技術能做到嗎?
//首先讀取配置文件
Properties properties = new Properties();
File file = new File("./Reflection/src/re.properties");
properties.load(new FileReader(file));
//可以獲得類名和包路徑,以及方法名,但現有方法無法利用這些信息重構該類或者調用方法
String classFullPath = properties.getProperty("classfullpath");
String methodName = properties.getProperty("method");
//new classFullPath(); //使用傳統方法無法利用String類型的報名創建類
//使用反射機制
//1. 載入類,返回Class類型的對象
Class<?> clazz = Class.forName(classFullPath);
System.out.println(clazz);
//2. 通過aClass 的到載入的類 com.hiibird.Cat 的對象實例
Object o = clazz.getDeclaredConstructor().newInstance();
System.out.println("o的運行類型:" + o.getClass());//o的運行時類型還是com.hiibird.Cat
//3. 通過getMethod()方法得到載入的類 com.hiibird.Cat 的methodName "hi" 的方法對象
//即:在反射中,可以把方法視為對象(萬物皆對象)
Method method = clazz.getMethod(methodName);
//4. 調用:通過method 調用方法:即通過方法對象來實現調用方法
method.invoke(o); //傳統方法 對象.方法();反射機制:方法.invoke(對象)
這樣的需求在學習框架時特別多,即通過外部配置文件,在不修改源碼的情況下來控製程序,也符合設計模式的ocp原則(開閉原則:不修改源碼,擴容功能)
1. 反射機制
- 反射機制允許程式員在執行期藉助於Reflection API取得任何類的內部信息(比如成員變數,構造器和成員方法等),並能操作對象的屬性及方法。反射在設計模式和框架底層都會用到。
- 載入完類之後,在堆中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象包含了類的完整結構信息。通過這個對象得到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以形象的稱之為反射。
1.1 Java反射機制的作用及優缺點
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時得到任意一個類所具有的成員變數和方法
- 在運行時調用任意一個對象的成員變數和方法
- 生成動態代理
反射機制的優缺點:優點:可以動態的創建和使用對象(也是框架底層核心),使用靈活,沒有反射機制,框架技術就失去底層支撐;缺點:使用反射基本是解釋執行,對執行速度有影響
1.2 反射相關的主要類
- java.lang.Class:代表一個類,Class對象表示某個類載入後在堆中的對象
Class<?> clazz = Class.forname(classfullpath);
Object instance = clazz.getDeclaredConstructor().newInstance();
- java.lang.reflect.Method:代表類的方法,Method對象表示某個類的方法
//傳統寫法:instance.method(),反射:method.invoke(instance);
Method method = clazz.getMethod(methodName);
method.invoke(instance);
- java.lang.reflect.Field:代表類的成員方法,Field對象標識某個類的成員變數。getField得不到私有的成員變數
//傳統寫法:instance.field,反射:field.get(instance)
Field field = clazz.getField(fieldName);
System.out.println(nameField.get(instance));
- java.lang.reflect.Constructor:代表類的構造方法,Constructor對象表示構造器
Constructor<?> constructor = clazz.getConstructor();//返回無參構造器
//通過指定構造器參數類型,獲取有參構造器
Constructor constructor1 = clazz.getConstructor(String.class, Integer.class);
1.3 反射調用優化-關閉訪問檢查
- Method和Field、Constructor對象都有setAceessible()方法
- setAccessible作用是開啟和禁用訪問安全檢查的開關
- 參數值為true表示反射的對象在使用時取消訪問檢查,提高反射的效率。參數值為false則表示反射的對象執行訪問檢查
method.setAccessible(true);//在反射調用時取消訪問檢查,可以稍微提高性能
method.invoke(instance);
2. Class類
類定義:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement
基本介紹:
- Class也是類,因此也繼承Object類
- Class類對象不是new出來的,其構造方法是私有的,只有JVM可以創建Class類對象
- 對於某個類的Class類對象,在記憶體中只有一份,因為類只載入一次,這個對象在類載入時由JVM創建
- 每個類的實例都會記得自己是由那個Class實例所生成
- 通過Class可以完成地得到一個類的完整結構,通過一系列API
- Class對象是存放在堆的
- 類的位元組碼二進位數據,是存放在方法區的,有的地方稱為類的元數據(包括方法代碼,變數名,方法名,訪問許可權等等)
2.1 Class類的常用方法
方法名 | 功能說明 |
---|---|
static Class forName(String name) | 返回指定類名name的Class對象 |
Object getConstructor().newInstance() | 調用預設構造函數,返回該Class對象的一個實例 |
getName() | 返回此Class對象所表示的實體(類、介面、數組類、基本類型等)名稱 |
class getSuperclass() | 返回當前Class對象的超類的Class對象 |
Class[] getInterface() | 返回當前Class對象的介面 |
ClassLoader getClassLoader() | 返回該類的類載入器 |
Constructor[] getConstructors() | 返回一個包含某些Constructor對象的數組 |
Field[] getDeclaredFields() | 返回Field對象的一個數組 |
Method getMethod(String name, Class ... paramType) | 返回一個Method對象,此對象的形參為paramType |
2.2 獲取Class對象的6中方式
- 前提:已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能拋出ClassNotFoundException。多用於配置文件,讀取類全路徑,載入類。實例:
Class clazz = Class.forName("java.lang.Cat");
- 前提:已知具體的類,通過類的class獲取,該方式最為安全可靠,程式性能最高。多用於參數傳遞,比如通過反射得到對應構造器對象。實例:
Class<Cat> clazz = Cat.class;
//用來傳遞參數
Constructor constructor = clazz.getConstructor(String.class, Integer.class);
- 前提:已知某個類的實例,調用該實例的getClass()方法獲取Class對象,多用於通過創建好的對象,獲取Class對象。實例:
Class<> clazz = instance.getClass();
- 其他方式:通過類載入器(有4種)獲取Class對象
ClassLoader cl = instance.getClass().getClassLoader();
Class clazz = cl.loadClass("classFullPath");
- 基本數據(int,char,boolean,float,double,byte,long,short)按如下方式得到Class對象:
Class<Integer> clazz = int.class;
- 基本數據類型對應的包裝類,可以通過.type得到Class對象:
Class<Integer> clazz = Integer.TYPE;
2.3 哪些類型有Class對象
- 外部類,成員內部類,靜態內部類,局部內部類,匿名內部類
- interface:介面
- 數組
- enum:枚舉
- annotation:註解
- 基本數據類型
- void
2.3 通過反射獲取類的結構信息
java.lang.Class類
- getName:獲取全類名
- getSimpleName:獲取簡單類名
- getFields:獲取所有public修飾的屬性,包含本類以及父類的
- getDeclaredFields:獲取本類中所有屬性
- getMethods:獲取所有public修飾的方法,包含本類以及父類的
- getDeclaredMethods:獲取本類中所有的方法
- getConstructors:獲取本類所有public修飾的構造器,不包含父類的
- getDeclaredConstructors:獲取本類中所有的構造器
- getPackage:以Package的形式返回包信息
- getSuperClass:以Class形式返回父類信息
- getInterfaces:以Class形式返回介面信息
- getAnnotations:以Annotations[]形式返回註解信息
java.lang.reflect.Field類
- getModifiers:以interesting形式返回修飾符【說明:預設修飾符是0,public是1,private是2,protected是4,static是8,final是16】:public static... -> 1+8 = 9
- getType:以Class形式返回類型
- getName:返回屬性名
java.lang.reflect.Method類
- getModifiers:以int形式返回修飾符【說明:預設修飾符是0,public是1,private是2,protected是4,static是8,final是16】:public static... -> 1+8 = 9
- getReturnType:以Class形式獲取 返回類型
- getName:返回方法名
- getParameterTypes:以Class[]形式返回參數類型數組
java.lang.reflect.Constructor類
- getModifiers:以int形式返回修飾符
- getName:返回構造器名(全類名)
- getParameterType:以Class[]返回參數類型數據
2.4. 通過反射創建對象
- 方法一:調用類中的public修飾的無參構造器
- 方法二:調用類中的指定構造器
- Class類相關方法:
- getDeclaredConstructor().newInstance():調用類中的無參構造器,獲取相應類的對象
- getConstructor(Class...clazz):根據參數列表,獲取對應的構造器對象
- getDeclaredConstructor(Class...clazz):根據參數列表,獲取對應的構造器對象
- Constructor類相關方法:
- setAccessible:暴破
- newInstance(Object...obj):調用構造器
2.5 通過反射訪問類中的成員
- 根據屬性名獲取Field對象:Field f = clazz.getDeclaredField(屬性名);
- 暴破: f.setAccessible(true); //f是Field對象
- 訪問 f.set(instance,value); f.get(instance);
- 註意如果是靜態屬性,則set和get中的參數o,可以寫成null
2.6 通過反射訪問類中的方法
- 根據方法名和參數列表獲取Method方法對象:
Method m = clazz.getDeclaredMethod(方法名, XX.class); - 獲取對象:Object o = clazz.getDeclaredConstructor().newInstance();
- 爆破:m.setAccessible(true);
- 訪問:Object returnValue = m.invoke(o, 實參列表);
- 在反射中,如果方法有返回值,統一返回Object
- 註意:如果是靜態方法,則invoke的參數o,也可以寫成null
3 類載入
基本說明:反射機制是java實現動態語言的關鍵,也就是通過反射實現類動態載入。
- 靜態載入:編譯時載入相關的類,如果沒有則報錯,依賴性太強
- 動態載入:運行時載入需要的類,如果運行時不用該類,即使不存在該類,也不會報錯,降低了依賴性
3.1 類載入時機
- 當創建對象時(new)//靜態載入
- 當子類被載入時,父類也被載入//靜態載入
- 調用類的靜態成員時//靜態載入
- 通過反射//動態載入