Java 反射基礎
反射 (Reflection) 是 Java 的特徵之一,它允許執行中的 Java 程式獲取自身的資訊,並且可以操作類或物件的內部屬性。
總而言之,通過反射,我們可以在執行時獲得程式或程式集中每一個型別的成員和成員的資訊
。程式中一般的物件的型別都是在編譯期就確定下來的,而Java 反射機制可以動態地建立物件並呼叫其屬性
,這樣的物件的型別在編譯期是未知的。
反射的核心是 JVM 在執行時才動態載入類或呼叫方法/訪問屬性,它不需要事先(寫程式碼的時候或編譯期)知道執行物件是誰。
1.1 反射機制主要提供以下功能
執行時 執行時 執行時 執行時
當我們的程式在執行時,需要動態的載入一些類,這些類可能之前用不到所以不用載入到jvm,而是在執行時根據需要才載入。
例如我們的專案底層有時是用mysql,有時用oracle,需要動態地根據實際情況載入驅動類,這個時候反射就有用了,假設 com.java.dbtest.mySqlConnection,com.java.dbtest.oracleConnection這兩個類我們要用,這時候我們的程式就寫得比較動態化,通過Class tc = Class.forName("com.java.dbtest.mySqlConnection");通過類的全類名讓jvm在伺服器中找到並載入這個類,而如果是oracle則傳入的引數就變成另一個了。這時候就可以看到反射的好處了,這個動態性就體現出java的特性了!
二、反射的主要用途
很多人都認為反射在實際的 Java 開發應用中並不廣泛,其實不然。當我們在使用 IDE時,我們輸入一個物件或類並想呼叫它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裡就會用到反射。
反射最重要的用途就是開發各種通用框架
。很多框架(比如 Spring)都是配置化的(比如通過 XML 檔案配置 Bean),為了保證框架的通用性,它們可能需要根據配置檔案載入不同的物件或類,呼叫不同的方法,這個時候就必須用到反射,執行時動態載入需要載入的物件。
三、反射的基本運用
上面我們提到了反射可以用於判斷任意物件所屬的類,獲得Class物件,構造任意一個物件以及呼叫一個物件。
3.1Java 的反射機制的實現藉助於4個類:class,Constructor,Field,Method;
其中class代表的是類物件,Constructor-類的構造器物件,Field-類的屬性物件,Method-類的方法物件,通過這四個物件我們可以粗略的看到一個類的各個組成部分。其中最核心的就是Class類,它是實現反射的基礎,它包含的方法我們在第一部分已經進行了基本的闡述。應用反射時我們最關心的一般是一個類的構造器、屬性和方法,下面我們主要介紹Class類中針對這三個元素的方法:
3.2 獲得Class物件
使用 Class 類的 forName 靜態方法:
public static Class<?> forName(String className) 比如在 JDBC 開發中常用此方法載入資料庫驅動: Class.forName(driver); 複製程式碼
直接獲取某一個物件的 class
Class<?> klass = int.class; Class<?> classInt = Integer.TYPE; 複製程式碼
呼叫某個物件的 getClass() 方法
StringBuilder str = new StringBuilder("123"); Class<?> klass = str.getClass(); 複製程式碼
3.3 判斷是否為某個類的例項
一般地,我們用instanceof
關鍵字來判斷是否為某個類的例項。同時我們也可以藉助反射中Class
物件的 isInstance() 方法來判斷是否為某個類的例項,它是一個 native 方法:
public native boolean isInstance(Object obj); 複製程式碼
3.4 建立例項
使用Class物件的newInstance()方法來建立Class物件對應類的例項
Class<?> c = String.class; Object str = c.newInstance(); 複製程式碼
先通過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立例項。這種方法可以用指定的構造器構造類的例項。
//獲取String所對應的Class物件 Class<?> c = String.class; //獲取String類帶一個String引數的構造器 Constructor constructor = c.getConstructor(String.class); //根據構造器建立例項 Object obj = constructor.newInstance("23333"); System.out.println(obj); 複製程式碼
3.5 獲取方法
getMethods()
返回某個類的所有public
方法,包括自己宣告和從父類繼承的
。
getDeclaredMethods()
獲取所有本類自己的方法,不問訪問許可權,不包括從父類繼承的方法
getMethod(String name, Class<?>... parameterTypes)
方法返回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件。
Method getDeclaredMethod(String name, Class<?>... params)
方法返回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件
操作私有方法
/** * 訪問物件的私有方法 * 為簡潔程式碼,在方法上丟擲總的異常,實際開發別這樣 */ private static void getPrivateMethod() throws Exception{ //1. 獲取 Class 類例項 TestClass testClass = new TestClass(); Class mClass = testClass.getClass(); //2. 獲取私有方法 //第一個引數為要獲取的私有方法的名稱 //第二個為要獲取方法的引數的型別,引數為 Class...,沒有引數就是null //方法引數也可這麼寫 :new Class[]{String.class , int.class} Method privateMethod = mClass.getDeclaredMethod("privateMethod", String.class, int.class); //3. 開始操作方法 if (privateMethod != null) { //獲取私有方法的訪問權 //只是獲取訪問權,並不是修改實際許可權 privateMethod.setAccessible(true); //使用 invoke 反射呼叫私有方法 //privateMethod 是獲取到的私有方法 //testClass 要操作的物件 //後面兩個引數傳實參 privateMethod.invoke(testClass, "Java Reflect ", 666); } } 複製程式碼
3.6 獲取建構函式
Constructor[] getConstructors()
獲得類的所有公共建構函式
Constructor getConstructor(Class[] params)
獲得使用特殊的引數型別的公共建構函式,
Constructor[] getDeclaredConstructors()
獲得類的所有建構函式
Constructor getDeclaredConstructor(Class[] params)
獲得使用特定引數型別的建構函式
3.7 獲取成員變數欄位
Field[] getFields()
獲得類的所有公共欄位
Field getField(String name)
獲得命名的公共欄位
Field[] getDeclaredFields()
獲得類宣告的所有欄位
Field getDeclaredField(String name)
獲得類宣告的命名的欄位
修改私有變數
** * 修改物件私有變數的值 * 為簡潔程式碼,在方法上丟擲總的異常 */ private static void modifyPrivateFiled() throws Exception { //1. 獲取 Class 類例項 TestClass testClass = new TestClass(); Class mClass = testClass.getClass(); //2. 獲取私有變數 Field privateField = mClass.getDeclaredField("MSG"); //3. 操作私有變數 if (privateField != null) { //獲取私有變數的訪問權 privateField.setAccessible(true); //修改私有變數,並輸出以測試 System.out.println("Before Modify:MSG = " + testClass.getMsg()); //呼叫 set(object , value) 修改變數的值 //privateField 是獲取到的私有變數 //testClass 要操作的物件 //"Modified" 為要修改成的值 privateField.set(testClass, "Modified"); System.out.println("After Modify:MSG = " + testClass.getMsg()); } } 複製程式碼
3.8 呼叫方法
當我們從類中獲取了一個方法後,我們就可以用 invoke() 方法來呼叫這個方法。invoke 方法的原型為:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 複製程式碼
下面是一個例項
public class test1 { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class<?> klass = methodClass.class; //建立methodClass的例項 Object obj = klass.newInstance(); //獲取methodClass類的add方法 Method method = klass.getMethod("add",int.class,int.class); //呼叫method對應的方法 => add(1,4) Object result = method.invoke(obj,1,4); System.out.println(result); } } class methodClass { public final int fuck = 3; public int add(int a,int b) { return a+b; } public int sub(int a,int b) { return a+b; } } 複製程式碼
3.9 利用反射建立陣列
陣列在Java裡是比較特殊的一種型別,它可以賦值給一個Object Reference。下面我們看一看利用反射建立陣列的例子:
public static void testArray() throws ClassNotFoundException { Class<?> cls = Class.forName("java.lang.String"); Object array = Array.newInstance(cls,25); //往數組裡新增內容 Array.set(array,0,"hello"); Array.set(array,1,"Java"); Array.set(array,2,"fuck"); Array.set(array,3,"Scala"); Array.set(array,4,"Clojure"); //獲取某一項的內容 System.out.println(Array.get(array,3)); } 複製程式碼
其中的Array類為java.lang.reflect.Array類。我們通過Array.newInstance()建立陣列物件,它的原型是:
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException { return newArray(componentType, length); } 複製程式碼
而 newArray 方法是一個 native 方法
private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException; 複製程式碼