你必須瞭解的Java:反射
首先,反射是框架設計的靈魂 ,這句話的分量,讀者自行體會。
1、幾個概念:
- 框架:半成品軟件。可以在框架的基礎上進行軟件開發,簡化編碼;所謂框架,就是能夠適應所有的情況,情況的不同,只需在配置文件中進行修改,而不是在原代碼(這個指的框架)中修改;
- 反射:將類的各個組成部分封裝爲其他對象,這就是反射機制。
- 好處:
-
- 可以在程序運行過程中,操作這些對象。idea 就是在程序的運行過程中,運用了反射的機制。 比如:定義了一個字符串,結果字符串的方法就已經可以選取調用了。·因爲在這個過程中,idea是一邊寫,一邊編譯,將字符串編譯爲字節碼文件,並且已經將字節碼文件通過ClassLoader加載進了內存中;
-
2. **可以解耦,提高程序的可擴展性。**
-
2、java在計算機中經歷的三個階段:
java代碼 ------>javac進行編譯------>字節碼文件.class------>classLoader類加載器------>在堆中創建對象 new someoneClass
source源代碼階段 class類對象階段 RUNTIME運行時階段
3、反射中的對象:
1、成員變量 Field[] field
2、構造方法 Constructor[]
3、成員方法 Method[] method
4、獲取Class對象的方式
-
Class.forName(“全類名”):將字節碼文件加載進內存,返回Class對象
多用於配置文件,將類名定義在配置文件中,配置文件中寫的都是全類名。讀取文件,加載類; -
通過類名屬性class獲得:( 多用於參數的傳遞)
// ClassName.class 不執行靜態塊和不執行動態塊兒
Class<?> class = ClassName.class;
- 對象.getClass() 封裝在object中:getClass()方法在Object類中定義着。
多用於對象的獲取該類的 字節碼文件 的方式
注意: 前兩個是直接根據類名獲取class文件 區別是全類名 或者類名(與絕對路徑和相對路徑相似)第三個是需要先創建類的對象,再有對象獲取類的字節碼文件。
結論: 三種獲取的方式都是同一個字節碼文件(*.class)。在一次程序運行過程中,一個類只會被加載一次,所以不論通過哪一種方式獲取的Class對象都是同一個。
靜態代碼塊與動態代碼塊在 獲取class文件中的應用:
首先定義一個測試類:
public class ClassTest {
static{
// 靜態代碼塊
System.out.println("靜態代碼塊兒...");
}
{
// 動態代碼塊
System.out.println("動態構造塊兒...");
}
public ClassTest(){
// 類中的構造方法
System.out.println("構造方法...");
}
}
使用第1種方法:
public class MainTest {
public static void main(String[] args) {
try {
Class<?> calss = Class.forName("com.souche.lease.admin.mytest.reflect.ClassTest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/* 打印結果是:
* 靜態代碼塊兒...
* 說明Class.forName("類名全路徑")執行靜態塊但是不執行動態塊兒(需要異常處理)
*/
使用第2種方法:
public class MainTest {
public static void main(String[] args) {
Class<?> calss = ClassTest.class;
}
}
/**
* 打印結果是:
* 什麼都沒打印
* 說明ClassName.class不執行靜態塊和不執行動態塊兒
*/
使用第3種方法:
public class MainTest {
public static void main(String[] args) {
Class<?> calss = new ClassTest().getClass();
}
}
/**
* 打印結果是:
* 靜態代碼塊兒...
動態構造塊兒...
構造方法...
* 說明對象.getClass()執行靜態塊也執行動態塊兒
*/
關於獲取class文件方法不同的總結:
第1種方法:類字面常量使得創建Class對象的引用時不會自動地初始化該對象,而是按照之前提到的加載,鏈接,初始化三個步驟,這三個步驟是個懶加載的過程,不使用的時候就不加載。
第2種方法:Class類自帶的方法。
第3種方法:是所有的對象都能夠使用的方法,因爲getClass()方法是Object類的方法,所有的類都繼承了Object,因此所有類的對象也都具有getClass()方法。
建議:使用類名.class,這樣做即簡單又安全,因爲在編譯時就會受到檢查,因此不需要置於try語句塊中,並且它根除了對forName()方法的調用,所以也更高效。
補充注意:靜態塊僅在類加載時執行一次,若類已加載便不再重複執行;而動態構造塊在每次new對象時均會執行。
補充:生成對象四種方式
請參見blog: https://blog.csdn.net/qq_39817135/article/details/101313225
5、Class對象功能( 就是獲取class文件之後,拿它來做什麼。)
1. 獲取 成員變量們 field
獲取了成員變量之後,就可以將獲取的成員變量進行設置、獲取(也就是get和set)原類中成員變量的值。從而讓其達到不創建對象就可以操作類中成員變量Field,構造方法Constructor,成員方法Method三個大的類對象;
Field[] getFields(): // 獲取所有 public 修飾的成員變量
Field getField(String name) // 獲取的指定的成員變量 獲取指定名稱的 public修飾的成員變量
Field[] getDeclaredFields() // 獲取所有的成員變量,不考慮修飾符
Field getDeclaredField(String name) //獲取的指定的成員變量
Field[] getDeclaredFields(); //獲取所有的成員變量,不考慮修飾符
2. 舉例說明 成員變量類對象 的獲取,並設置、獲取 成員變量類對象中的值
// e.g. 成員變量類對象 的獲取,並設置、獲取 成員變量類對象中的值
Field[] declaredFields = personClass.getDeclaredFields();
// 獲取了所有的成員變量,並且將其放在了數組中;並可以將其遍歷:
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
// 獲取指定的成員變量,如果成員變量是私有的,那麼需要將獲取權限 即暴力反射獲得權限
Field getDeclaredField(String name)
Field d = personClass.getDeclaredField("d");
// 成員變量類對象d 就可以進行d.get();d.set();
// 忽略訪問權限修飾符的安全檢查
d.setAccessible(true); //暴力反射
d.set(oo, "wangwu")
Object value2 = d.get(p);
System.out.println(value2);
3、獲取構造方法們 :之後就創建對象
Constructor<?>[] getConstructors() //獲取的是空參構造
Constructor<T> getConstructor(類<?>... parameterTypes)
// 獲取的是有參構造
Constructor<T> getDeclaredConstructor(類<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
//Constructor<T> getConstructor(類<?>... parameterTypes)
Constructor constructor = personClass.getConstructor(String.class, int.class);
//這裏返回得到的是 構造器
//這裏傳遞的參數
//String.class,int.class是原構造方法中參數類型的class對象
System.out.println(constructor);
/**創建對象****重要:constructor.newInstance("張三", 23)
**當然也可以利用空參構造,創建對象:但是提供了比較簡單的方法:
**直接跳過獲取構造器,直接使用類名就可以利用空參構造的創建對象:
** Class對象的newInstance方法
** personClass.newInstance()*/
Object person = constructor.newInstance("張三", 23);
System.out.println(person);
4、 獲取成員方法們:之後就執行方法
Method[] getMethods()
Method getMethod(String name, 類<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 類<?>... parameterTypes)
//獲取指定名稱的方法 其實是總的方法類對象
Method eat_method = personClass.getMethod("eat");
// 這裏是無參方法,可以不用傳遞參數類型的對象
Person p = new Person();
//獲取到成員方法之後,需要執行方法,關鍵詞:.invoke(p)
//獲取的法類對象來執行 需要傳遞的是一個對象,以及該方法執行所需的參數類對象
eat_method.invoke(p); // 這裏是無參方法,可以不用傳遞參數類型的對象
// 有參方法舉例並說明:
Method eat_method = personClass.getMethod("eat",String.class,int.class,...);
eat_method.invoke(p,"abcdef",6);
//獲取所有public修飾的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
String name = method.getName();
//獲取所有的方法名,
//這裏獲取的方法名不僅僅是方法中的public修飾的方法名,
//還有其繼承Object中的方法
System.out.println(name);
//method.setAccessible(true);
}
}
/* Field:成員變量
獲取了成員變量之後,就可以用將獲取的成員變量進行設置、獲取原類中成員變量的值。
這樣就可以 不通過構造方法,對目標類(最開始的類)進行獲取或者設置成員變量的值。*/
// 操作:
//1. 設置值
* void set(Object obj, Object value)
//2. 獲取值
* get(Object obj)
//3. 忽略訪問權限修飾符的安全檢查
* setAccessible(true): 暴力反射(私有的成員變量都可以訪問)
// Constructor:構造方法
// 創建對象:
T newInstance(Object... initargs)
// 如果使用空參數構造方法創建對象,操作可以簡化:
Class對象的newInstance方法
// Method:方法對象
// 執行方法:
Object invoke(Object obj, Object... args)
// 獲取方法名稱:
String getName:獲取方法名
面試題:
需求:
寫一個"框架",不能改變該類的任何代碼的前提下,
可以幫我們創建任意類的對象,並且執行其中任意方法。
回答思路
實現:
-
配置文件 properties
-
反射(如果配置文件中 出現了全類名,絕大部分情況就是運用了反射)
步驟:
- 將需要創建的對象的全類名和需要執行的方法定義在配置文件中
- 在程序中加載讀取配置文件
- 使用反射技術來加載類文件進內存
- 創建對象
- 執行方法