反射的作用
開門見山地說說反射的作用
1.為我們提供了全面的分析類信息的能力
2.動態加載類
我理解的“反射”的意義
(僅個人理解哈)
我理解的java反射機制就是: 提供一套完善而強大的API“反射“類的結構。
打個比方,反射機制就像是一面鏡子,而類就像是一個在照着鏡子的人。
鏡子(反射機制)照出(反射)了人的全貌(類的全方位的信息,例如方法,成員變量和構造器等的相關信息)
為什么要照鏡子? 因為不照鏡子看不清楚自己的全貌,“鏡子”就是為了解決這個問題出現的(為我們提供全面分析類的能力)
好吧,我知道這聽起來還是很模糊,讓我們一步一步來:
類也是對象
在java里有一句話:萬物皆對象, 即使是int等基本類型,雖然本質上不是對象,但行為卻也和對象密切相關(基本包裝類型和自動裝箱)
所以有一個可能完全打破我們常規思維的論斷是: 類也是對象
“類”對象和“類”類型
好吧,其實說“ 類也是對象”並不太好,而應該說,java中每個類都有一個與之對應的“類”對象(Class對象),這個“類”對象由jvm生成,並保存了對應類的相關信息。例如,假設我們的java文件涉及三個類:a類,b類和c類,那么編譯的時候就會對應生成a類的“類”對象,a類的“類”對象,a類的“類”對象,分別用於保存和a,b,c類對應的信息
我們的思維是這樣的: 一個對象必然有一個與之對應的類,因為只有類才能實例化對象啊
那么,“類對象”的“上面”,應該還有一個類才對!這個“類之上的類”,就是java.lang.Class,它是所有“類”對象的類(這樣說可能聽起來很拗口)
我們這樣聲明一個“類”對象,假設這個類對象(Class對象)是a——
Class a
我們稱a屬於“類”類型(Class類型)
所以我們可以其實可以將java中的對象分為兩種:
1. 實例對象
2. Class對象
所以我們今天要講的第一個內容是: 有別於平時使用的實例對象的——Class對象
取得Class對象的三種方式
我們假設有這么一個類叫MyClass:
public class MyClass { }
那么取得該類對應Class對象的方法有三種:
一. 通過“類名.class”的方式取得
Class classInstance= MyClass.class;
二. 通過類創建的實例對象的getClass方法取得
MyClass myClass = new MyClass(); Class classInstance = myClass.getClass();
三.通過Class類的靜態方法forName方法取得(參數是帶包名的完整的類名)
Class classInstance = Class.forName("mypackage.MyClass");
【注意】
1.運行forName時候可能會因為找不到包名而拋出已檢查異常ClassNotFoundException,所以我們需要將其包裹在try-catch語句中:
try { Class classInstance = Class.forName("mypackage.MyClass"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }
2.上面三種方法取得的對象都是相同的,所以效果上等價
利用反射API全面分析類的信息——方法,成員變量,構造器
我們上面提到,反射的一大作用是用於分析類的結構,或者說用於分析和這個類有關的所有信息。而這些信息就是類的基本的組成: 方法,成員變量和構造器
事實上,和我們上面所介紹的Class類和Class對象相似的是,
一個類中的方法,成員變量和構造器也分別對應着一個對象
1.每個方法都對應有一個保存和該方法有關信息的Method對象, 這個對象所屬的類是java.lang.reflect.Method;
2.每個成員變量都對應有一個保存和該變量有關信息的Field對象,這個對象所屬的類是 java.lang.reflect.Field
3. 每個構造器都對應有一個保存和該構造器有關信息的Constructor對象,這個對象所屬的類是java.lang.reflect.Constructor
方法,成員變量和構造器是附屬於某一個類的,正因如此,我們應該先取得某一個類對應的Class對象,其次才考慮如何取得 Method/Field/Constructor對象

我們可以通過一系列的方法,從一個類的Class對象中取得對應的Method對象,Field對象和Constructor對象
假設c是一個類的Class對象:
通過 c.getDeclaredMethods()可取得這個類中所有聲明方法對應的Method對象組成的數組
通過 c.getDeclaredFields()可取得這個類中所有聲明的成員變量對應的Field對象組成的數組
通過 c.getConstructors(); 可取得這個類中所有構造函數所對應的Constructor對象所組成的數組
在下面的示例中,我們將遍歷某一個類中方法,成員變量和構造器的名稱:
MyClass.java:
public class MyClass { private int value; //成員變量 public MyClass (int value) { this.value = value; } //構造函數 public int getValue() { return value; } //方法1 public void setValue(int value) { this.value = value; } //方法2 }
Test.java:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test { public static void printClassMessage (Object obj) { Class c = obj.getClass(); // 獲取obj所屬類的Class對象
Method [] methods = c.getDeclaredMethods(); // 獲取方法對象列表
System.out.println("遍歷MyClass類里的所有方法的名稱:"); for(int i =0; i<methods.length; i++) { System.out.println(methods[i].getName()); }
Field [] fields = c.getDeclaredFields(); // 獲取成員變量對象列表 System.out.println("遍歷MyClass類里的所有成員變量的名稱:"); for(int i =0; i<fields.length; i++) { System.out.println(fields[i].getName()); } Constructor [] constructors = c.getConstructors(); // 獲取構造函數對象列表 System.out.println("遍歷MyClass類里的所有構造函數的名稱:"); for(int i =0; i<constructors.length; i++) { System.out.println(constructors[i].getName()); } }
public static void main(String [] args) { MyClass myClass = new MyClass(1); // 創建一個MyClass對象 printClassMessage(myClass); // 打印這個對象所屬類的相關信息 } }
運行結果:
遍歷MyClass類里的所有方法的名稱:
getValue
setValue
遍歷MyClass類里的所有成員變量的名稱:
value
遍歷MyClass類里的所有構造函數的名稱:
mypackage.MyClass
上面的例子僅僅是作為一個展示,Method/Field/Constructor對象的API當然不僅限於getName這樣獲取名稱的簡單操作,所以接下來我將分別介紹更具體的反射API
利用反射API分析類中方法信息
getMethods和getDeclaredMethods方法
getMethods和getDeclaredMethods的區別在於:
getMethods取得的method對應的方法包括從父類中繼承的那一部分,而
getDeclaredMethods取得的method對應的方法不包括從父類中繼承的那一部分
例如上面通過打印getDeclaredMethods打印的MyClass的方法信息:
getValue
setValue
讓我們看看通過getMethods打印又會取得什么結果:
Test.java:
import java.lang.reflect.Method; public class Test { public static void printMethodsMessage (Object obj) { Class c = obj.getClass(); Method [] methods = c.getMethods(); for (Method method : methods) { System.out.println(method.getName()); } }
public static void main(String [] args) { MyClass myClass = new MyClass(1); printMethodsMessage(myClass); } }
運行結果:
getValue
setValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
讓我們思考一下: 一個方法有哪些“信息”值得(或需要)我們去分析呢?
主要是兩部分:
1. 返回值
2. 方法參數
你可能猜的出來, Method對象已經提供了一套API去獲取這些信息了,讓我們來看看:
通過method.getReturnType()獲取方法返回值對應的Class對象
import java.lang.reflect.Method; public class Test { public static void printMethodsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所屬類對應的Class對象 Method [] methods = c.getDeclaredMethods(); // 取得obj所屬類中方法對應的Method對象組成的數組 for (Method method : methods) { // 遍歷Method對象 String name = method.getName(); // 取得方法名 Class returnClass = method.getReturnType(); // 獲取方法返回值對應的Class對象 String returnName = returnClass.getName(); //獲取返回值所屬類的類名——也即返回值類型 System.out.println(name + "方法的返回值類型是" + returnName); } } public static void main(String [] args) { MyClass myClass = new MyClass(1); printMethodsMessage(myClass); }
}
運行結果:
getValue方法的返回值類型是int
setValue方法的返回值類型是void
通過method.getReturnType(),我們取得了該方法的返回值對應的Class對象(哈哈,繞了一圈后還是回到Class對象上了)
然后通過Class對象調用getName方法就取得了返回值所屬的類的名稱,也即返回值類型
通過method.getParameterTypes()獲取方法各參數的Class對象組成的數組
MyClass.java:
public class MyClass { public void method1 (int a, long b) { }; public void method2 (float a, double b) { }; public void method3 (String str) { }; }
Test.java:
public class Test { public static void printMethodsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所屬類對應的Class對象 Method [] methods = c.getDeclaredMethods(); // 取得obj所屬類中方法對應的Method對象組成的數組 for (Method method : methods) { // 遍歷Method對象 String methodName = method.getName(); // 取得方法名 String paramsStr = ""; // 用於存放某個方法參數類型列表的字符串 Class [] paramsClasses = method.getParameterTypes(); for (Class pc: paramsClasses) { String paramStr = pc.getName(); // 獲取當前參數類型 paramsStr+=paramStr + " "; } System.out.println(methodName+ "方法的所有參數的類型列表:" + paramsStr); } }
public static void main(String [] args) { MyClass myClass = new MyClass(); printMethodsMessage(myClass); } }
運行結果:
method2方法的參數類型列表:float double method1方法的參數類型列表:int long method3方法的參數類型列表:java.lang.String
利用反射API分析類中成員變量信息
獲取成員變量類型對應的的Class對象
讀取成員變量的值
MyClass.java:
public class MyClass { private int number = 123; private String name ="彭湖灣"; }
Test.java:
import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test { public static void printFieldsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所屬類對應的Class對象 try { Field field = c.getDeclaredField("name"); // 取得名稱為name的field對象 field.setAccessible(true); // 這一步很重要!!!設置為true才能訪問私有成員變量name的值! String nameValue = (String) field.get(obj); // 獲取obj中name成員變量的值 System.out.println("MyClass類中name成員變量的值為:" + nameValue); // 輸出 } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
public static void main(String [] args) { MyClass myClass = new MyClass(); printFieldsMessage(myClass); }
}
運行結果:
MyClass類中name成員變量的值為:彭湖灣
通過getType方法讀取成員變量類型的Class對象
這里只展現比較關鍵的兩段代碼:
Field field = class1.getDeclaredField(number"); System.out.print(field.getType().getName());
運行結果:
int
【注意】:因為java權限的原因,直接讀取私有成員變量的值是非法的(加了field.setAccessible(true)后就可以了),但仍可以直接讀取私有成員變量的類型
利用反射API分析類中構造器信息
分析構造函數的時候,其實思路大體上和分析方法時候一致,關鍵在於獲取參數所屬類的Class對象
區別在於:
1. 獲取該類聲明的構造器用的是getDeclaredConstructors方法而不是getDeclaredMethods方法
2. 構造函數沒有返回值,所以不需要分析返回值(我似乎說了廢話....)
廢話不多說了,看下面的例子:
MyClass.java:
public class MyClass { public MyClass(int a, String str){} }
Test.java:
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test { public static void printContructorsMessage (Object obj) { Class c = obj.getClass(); // 取得obj所屬類對應的Class對象 Constructor [] constructors = c.getDeclaredConstructors(); for (Constructor constructor : constructors) { Class [] paramsClasses = constructor.getParameterTypes(); String paramsStr = ""; for (Class pc : paramsClasses) { String paramStr = pc.getName(); paramsStr+=paramStr + " "; } System.out.println("構造函數的所有參數的類型列表:" + paramsStr); } } public static void main(String [] args) { MyClass myClass = new MyClass(1, "彭湖灣"); printContructorsMessage(myClass); }
}
運行結果:
構造函數的所有參數的類型列表:int java.lang.String
利用反射動態加載類,並用該類創建實例對象
動態加載類
我們用普通的方式使用一個類的時候,類是靜態加載的
而使用Class.forName("XXX")這種方式,則屬於動態加載一個類
靜態加載的類在編譯的時候就能確定該類是否存在,但動態加載一個類的時候卻無法在編譯階段確定是否存在該類,而是在運行時候才能夠確定是否有這個類,所以要捕捉可能發生的異常
我們可以從eclipse的使用上有個相對直觀的了解:
eclipse在保存的時候是可以自動編譯的,SO
例如我們如果直接使用一個本就不存在的類NotExistClass的時候


保存后,在編譯階段就能夠發現:“誒? 好像沒有這個類哦!”
報的錯誤是:
NotExistClass cannot be resolved to a type
但是如果我們用Class.forName("XXX")動態加載一個類呢?

我們發現,保存后,在編譯階段已經不能發現這個錯誤了,對應的是要捕捉可能發生的異常

用該動態加載的類創建實例對象
Class對象有一個newInstance方法,我們可以用它來創建實例對象
Class classInstance = Class.forName("mypackage.MyClass"); MyClass myClass = (MyClass) classInstance.newInstance();
不過要注意的是,因為newInstance返回的是一個Object,所以要做強制類型轉換,將其變成MyClass類型
捕捉可能產生的異常后:
public class Test { public static void main(String [] args) { try { Class classInstance = Class.forName("mypackage.MyClass"); MyClass myClass = (MyClass) classInstance.newInstance(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
全文的總結
如果您已經看到這里了,那么請允許我在用多那么一點羅嗦的文字做個總結:
總結
1.反射為我們提供了全面的分析類信息的能力,例如類的方法,成員變量和構造器等的相關信息,反射能夠讓我們很方便的獲取這些信息, 而實現這個獲取過程的關鍵是取得類的Class對象,然后根據Class對象取得相應的Method對象,Field對象和Constructor對象,再分別根據各自的API取得信息。
2.反射還為我們提供動態加載類的能力
一些細節
1. API中getDeclaredXXX和getXXX的區別在於前者只獲取本類聲明的XXX(如成員變量或方法),而不獲取超類中繼承的XXX, 后者相反
2. API中, getXXXs(注意后面的s)返回的是一個數組, 而對應的 getXXX("鍵")按鍵獲取一個值(這個時候因為可能報已檢查異常所以要用try-catch語句包裹)
3. 私有成員變量是不能直接獲取到值的!因為java本身的保護機制,允許你取得私有成員變量的類型,但是不允許直接獲取值,所以要對對應的field對象調用field.setAccessible(true) 放開權限
最后
反射的API其實還有一堆...但是抱歉的是沒辦法一一給大家介紹,只能當作個“引子”給大家嘗個鮮,另外,本人java小白,有諸多不當之處,還望大家指出,謝謝大家。
【完】
參考資料:
《java核心技術 卷1》—— Cay S. Horstmann, Gary Cornell
