反射
反射應用的非常廣泛,平時幾乎是天天在用了,不信?平時用 IDE 工具時最愛的操作就是輸入一個類或者物件引用的時候通過 .
,就可以看到 IDE 工具這個類提供的方法和屬性了,這個功能就是利用了反射。
比如 Struts2 和 Hibernate 這種框架就用的及其普遍,通常都是在 .xml 這種配置檔案中新增框架所需要的配置資訊,框架執行時將配置檔案載入到記憶體中後,讀取相應資訊到物件中進行儲存,最後利用反射技術動態建立所需要的物件。 Java/java-reflection-1/" target="_blank" rel="nofollow,noindex">參考這篇反射文章
類的載入
此前在學習 JavaSE 部分的時候,講到物件的建立過程時,第一步做的就是將該類的位元組碼檔案 .class 載入到記憶體中。 JVM 在載入一個類的位元組碼到記憶體中時,同時還會給這個類建立一個 java.lang.Class 型別的反射物件。 也就是說並不是只有通過 Class.forName()
、 類名.class
或者 Object.getClass()
這三種方式才會建立反射物件,其實類載入到記憶體的時候反射物件就已經在堆區存在了,只是只有通過這三種方式才能夠得到它的引用作為返回值返回而已。
到底該如何理解 java.lang.Class 這個型別呢?通常講類是一類物件的抽象,但是類同樣也是一種抽象的概念,類也有一些共同特徵,如果把它們抽象提取出來就可以用 java.lang.Class 類表示它們。看起來有點繞,這不就是所謂元資料的概念嗎?所謂元資料就是描述資料的資料,這個 Class 就是描述類的類唄。 參考元資料介紹
抽象理解一下,之所以把對於類的抽象用 Class 這個名字來表示,其實也是遵從命名規範。每一個 .java 原始檔都被編譯成一個 .class 的位元組碼檔案,那起一個什麼樣的名字來抽象代表這一類的 .class 位元組碼檔案呢?那就直接命名一個 java.lang.Class 型別的類啊。也就是說 JVM 管理的記憶體區域中,每一個 .class 檔案都有一個被稱之為反射物件的物件和它一一對應。
打個比方,.class 位元組碼檔案就好像是一個脫光了衣服的 Java 類,反射物件就代表了這個 .class 檔案的控制代碼,通過這個反射物件可以看到它身體的每一部分,也就是能夠得到這個類的所有信息。
總結就是:物件的抽象是類,類的抽象就是 Class 。每一個位元組碼檔案都有一個唯一的反射物件和它對應,通過這個反射物件能夠得到這個位元組碼檔案的所有資訊。

_反射.png
建立反射物件的三種方法
一個類可以有無數個物件,如果記憶體裝得下的話。但是一個類只能有一個反射物件,因為一個類的位元組碼檔案也只會被載入一次,這可以通過 ==
操作符對得到的對映物件進行驗證。
反射物件呼叫類的構造方法建立的物件是 Object 型別的,其實這也很正常啊,它也沒法事先指定資料型別啊。
反射物件的建立流程: 拿到類的全名 ,載入類對應的位元組碼檔案,如果有靜態程式碼塊就會先執行靜態程式碼塊的內容,接著立即建立一個 Class 型別的反射物件,也叫作對映物件。
通過反射技術建立物件,會額外消耗更多資源,所以只有在需要動態建立物件的時候才用。而且通過反射技術建立物件,會忽略許可權檢查,能夠通過反射物件直接使用私有屬性和方法,破壞了封裝性,還是不要隨便亂用的好。
第一種:Class.forName(String className)
第一種方式通過 java.lang.Class 類的靜態方法 forName(String className)
來載入類的位元組碼檔案並得到反射物件的引用。反射物件也是一個物件啊,它和普通的其他物件並沒有很大不同。
//第一種方式:通過載入類取得與該類對應的反射物件 Class c = Class.forName("java.util.Date"); Object obj = c.newInstance();
第二種:通過類的 class 屬性得到反射物件
這種方式的特殊之處在於,載入類的位元組碼檔案時,沒有進行任何初始化,連靜態程式碼塊內容都沒有執行,當然反射物件還是建立了的。
//第二種方式:通過類的class屬性取得與該類對應的反射物件 Class c2 = Employee.class; Object o = c2.newInstance();
第三種:通過物件的 getClass() 方法
此方法很明顯,是直接返回堆區已經存在反射物件的引用。
java.util.Date date = new java.util.Date(); Class c = date.getClass();
方法反射
在程式編寫階段不知道要呼叫物件的哪個方法時,可以使用方法反射技術等到程式執行階段再來執行具體的方法。
比如 struts1 中的 DispatchAction 類就是專門設計來避免應用中新增過多的 Action 類的。原來是一個請求對應一個Action,這樣導致配置量很大,Action類過多。現在一個請求對應 DispatchAction 子類中的一個方法,只需要定義一個類來繼承 DispatchAction 類並提供和請求對應的方法即可。完成此功能,就是通過解析 url ,從中得到包含的方法名,最後通過方法反射技術來呼叫對應的方法實現的。
方法反射具體步驟
- 得到反射物件
- 通過反射物件呼叫
Class. getDeclaredMethod(String name, Class<?>... parameterTypes)
方法返回一個代表方法的 Method 類物件- String 型別的形參 name 表示方法名
- 第二個形參 parameterTypes 是 Class 型別的可變引數,它接收該方法的引數列表的反射物件表示,比如 String.class,Integer.class 當形參過多時,可以將其儲存在一個數組上,然後以陣列的形式傳遞進來。可變引數本質上就是一個數組!
- 只有明確方法名和引數列表才能確定具體是哪個方法,所以這裡要傳遞這樣的引數進去。
- 通過 Method 類的
invoke(Object obj, Object... args)
方法來執行它代表的這個方法,返回的是此方法返回值的 Object 型別的表示。- 第一個形參 obj 表示一個例項物件,也就是說通過方法反射技術呼叫的是這個例項物件的方法。必須要加上它,因為方法不能脫離物件而存在的。
- 第二個形參 args 是 Object 型別的可變引數,它表示被呼叫方法的實參。呼叫一個例項物件的方法肯定要給它傳參啊!
- 只有明確呼叫的是哪個例項物件的方法並給它傳參,才能成功呼叫該方法
疑問: invoke()
方法返回的是一個 Object 型別的一個物件,那如果需要呼叫的是一個會返回陣列或者集合的方法呢?
回答:當時腦子短路了嗎?Object 是任何資料的基類啊,難道它不能夠代表陣列或者集合嗎?只是需要自己手動的去轉型而已,所以反射通常都是和介面組合使用的。這也體現了 Java 所倡導的面向介面程式設計的思想!
總結一下,實現方法反射需要三要素,分別是:方法名、呼叫方法的這個例項物件以及呼叫方法引數的資訊。
下面以 java.util.Date 類的 hashCode()
方法為例來展示下,方法反射的編寫過程。 不需要給可變引數傳值時時,什麼都不傳就可以了。
public static void main(String[] args) throws Exception { String className = "java.util.Date"; String methodName = "hashCode"; //得到反射物件 Class clazz = Class.forName(className); //通過反射物件建立例項物件 Object obj = clazz.newInstance(); //通過反射物件得到代表方法的Method物件 Method method = clazz.getDeclaredMethod(methodName); //通過Method物件呼叫invoke方法來執行指定的例項物件的方法 int hashCode = (Integer)method.invoke(obj); System.out.println(hashCode); //輸出2076132911 }