Java程式語言的後門-反射機制
// 通過反射機制,通知力巨集做事情 method.invoke(object, args);
// 通過反射,將h作為引數,例項化代理類,返回代理類例項。 return cons.newInstance(new Object[]{h});
而且在
// 將介面類物件陣列clone一份。 final Class<?>[] intfs = interfaces.clone();
也提到一個類物件陣列的概念,如果你不知道反射,不知道類物件,那麼你在閱讀者兩篇文章的時候,很可能就會雨裡霧裡,不知所然。通過這篇文章你就能很輕鬆的掌握類物件和反射。
反射是java語言中一個很基礎,很簡單的知識,不僅僅是在工作中會用到,而且也會經常出現在面試中。如果你認真閱讀本文,對你在技術層面來說又是一個提升。
一,前言
反射是什麼呢?其實是jiava程式語言的一種機制,我理解為是java的後門。在其他文章中給出的定義和解釋都比較晦澀難懂,不如先看一下具體的程式碼,再去理解,這樣就容易很多。
為了更好的理解反射機制,不得不提到類物件的概念,為了不與類和物件的概念搞混,我們就首先看一下類和物件的概念。詳細你已經非常熟悉類和物件的概念了,那麼我就簡單的描述一下類與物件的概念:
類:一個或一組事物的抽象描述,例如狗是對狗狗這一組事物的抽象描述。
物件:具體的某一個事物,例如哈士奇等,也可以說是類的一個例項。
那麼類物件是什麼呢?
在java中一切皆物件。既然一切皆物件,當然類也是一種物件,那麼類的物件型別是什麼呢?是java.lang.Class,你也許在其他的地方見到過。
那麼類物件是從哪裡來的,怎麼建立的呢?
我們都知道,想要得到一個類的物件,最基本的方法就是通過new關鍵字,去例項化一個物件。但類物件是一個特殊的物件,自然不能使用new關鍵字,翻看Class類的原始碼就可以證明:
/* * 私有化構造方法,只有java 虛擬機器才能建立類物件 * Private constructor. Only the Java Virtual Machine creates Class objects. * This constructor is not used and prevents the default constructor being * generated.*/ private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
Class類中只有這一個私有的構造方法。其實類物件是java虛擬機器(JVM)在載入class檔案的時候自動在虛擬機器記憶體中給我們建立的。
這裡就涉及到了另外一個機制:類載入機制。
簡單描述一下類載入機制:就是虛擬機器將class檔案載入到虛擬機器記憶體中,最終形成可以被虛擬機器直接使用的java型別。虛擬機器會將class檔案中的資訊按照所需的儲存格式放在方法區中,同時會在記憶體中(HotSpot是在堆記憶體中)例項化一個java.lang.Class類物件。
類記載機制是一個很複雜的過程,不是本篇文章的重點,就不展開來說。至少到這裡我們已經知道了,類物件是由虛擬機器建立的而且HotSpot虛擬機器將類物件存放在堆記憶體中。
那麼怎麼通過類物件來實現反射呢?為了理解方便,來舉一個有趣的例子
二,一個有趣的例子
有一天一個同事養了一隻狗狗哈士奇,在作者面前大肆炫耀,說他的狗狗多麼萌,多麼威武,多麼聽話......,作者聽完心生嚮往,提出去看一看。但是這個同事高傲的擡起頭說了三個字:想的美。
竟然不給我看!!!作者一氣之下,就走了java程式的後門-反射。你不讓我看,我偏偏要看。
哈士奇類的定義:
package com.zcz.reflecttest; public class HaShiQi implements Dog { public String color = "黑色"; private String name = "富貴"; //私有化構造 private HaShiQi() {}; @Override public void eat() { // TODO Auto-generated method stub System.out.println(name + " 去吃狗糧"); } @Override public void run() { // TODO Auto-generated method stub System.out.println(name + " 去跑著玩兒"); } private void dance() { System.out.println(name + " 來跳一支舞"); } @Override public String toString() { // TODO Auto-generated method stub return "名字:"+name+";顏色:"+color; } }
可以看到同時的哈士奇實現了Dog介面:
package com.zcz.reflecttest; public interface Dog { public void eat(); public void run(); }
從程式碼中可以看到,我的同事不僅僅沒告訴作者他的哈士奇竟然會跳舞,甚至連哈士奇的名字都不想讓作者知道,而且連構造器都私有化了。
那麼接下來,走後門開始。在上提到類物件,正好我想通過反射看狗狗,也需要用到類物件。那麼接下來第一步就先獲取HaShiQi的類物件。
三,類物件的獲取
超級簡單:
package com.zcz.reflecttest; /** * 通過反射檢視同事狗狗的資訊 * @author zhangchengzi * */ public class LookLook { public static void main(String[] args) { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; } }
從這裡開始我們的反射的使用的開始了,先看看HaShiQi類中有哪些屬性吧?
四,獲取屬性
程式碼:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; System.out.println("——————— 獲取所有公有的屬性 —————————"); Field[] fields = clazz.getFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } }
列印結果:
——————— 獲取所有公有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color
可以看到只有一個color屬性,使用getFields方法是獲取不到私有屬性了,想要獲取私有屬性就要使用下方的方法:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; System.out.println("——————— 獲取所有公有的屬性 —————————"); Field[] fields = clazz.getFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有的屬性 —————————"); fields = clazz.getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } }
列印結果:
——————— 獲取所有公有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color ——————— 獲取所有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color private java.lang.String com.zcz.reflecttest.HaShiQi.name
可以看到,HaShiQi類中私有(private修飾的)的name屬性打印出來了。
五,獲取方法
程式碼:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; System.out.println("——————— 獲取所有公有的屬性 —————————"); Field[] fields = clazz.getFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有的屬性 —————————"); fields = clazz.getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有公有的方法 —————————"); Method[] methods = clazz.getMethods(); for(Method method : methods) { System.out.println(method); } }
列印結果:
——————— 獲取所有公有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color ——————— 獲取所有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color private java.lang.String com.zcz.reflecttest.HaShiQi.name ——————— 獲取所有公有的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() //重寫的toString 方法 public java.lang.String com.zcz.reflecttest.HaShiQi.toString() public void com.zcz.reflecttest.HaShiQi.eat() //Object類中的方法 public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll()
從結果中可以發現,getMethod方法也是隻能獲取到公有的方法,而且連Object中的方法也獲取到了。跟獲取屬性也是一樣的,也有方法可以獲取到HaShiQi類中所有的方法:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; System.out.println("——————— 獲取所有公有的屬性 —————————"); Field[] fields = clazz.getFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有的屬性 —————————"); fields = clazz.getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有公有的方法 —————————"); Method[] methods = clazz.getMethods(); for(Method method : methods) { System.out.println(method); } System.out.println("——————— 獲取所有類中宣告的方法 —————————"); methods = clazz.getDeclaredMethods(); for(Method method : methods) { System.out.println(method); } }
列印結果:
——————— 獲取所有公有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color ——————— 獲取所有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color private java.lang.String com.zcz.reflecttest.HaShiQi.name ——————— 獲取所有公有的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() public java.lang.String com.zcz.reflecttest.HaShiQi.toString() public void com.zcz.reflecttest.HaShiQi.eat() public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ——————— 獲取所有類中宣告的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() public java.lang.String com.zcz.reflecttest.HaShiQi.toString() private void com.zcz.reflecttest.HaShiQi.dance() public void com.zcz.reflecttest.HaShiQi.eat()
這樣就可以同時獲取到HaShiQi類中的私有方法(private修飾的)dance了。
六,獲取構造器
程式碼:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; System.out.println("——————— 獲取所有公有的屬性 —————————"); Field[] fields = clazz.getFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有的屬性 —————————"); fields = clazz.getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有公有的方法 —————————"); Method[] methods = clazz.getMethods(); for(Method method : methods) { System.out.println(method); } System.out.println("——————— 獲取所有類中宣告的方法 —————————"); methods = clazz.getDeclaredMethods(); for(Method method : methods) { System.out.println(method); } System.out.println("——————— 獲取所有公有的構造器 —————————"); Constructor[] constructors = clazz.getConstructors(); for(Constructor constructor : constructors) { System.out.println(constructor); } }
列印結果:
——————— 獲取所有公有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color ——————— 獲取所有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color private java.lang.String com.zcz.reflecttest.HaShiQi.name ——————— 獲取所有公有的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() public java.lang.String com.zcz.reflecttest.HaShiQi.toString() public void com.zcz.reflecttest.HaShiQi.eat() public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ——————— 獲取所有類中宣告的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() public java.lang.String com.zcz.reflecttest.HaShiQi.toString() private void com.zcz.reflecttest.HaShiQi.dance() public void com.zcz.reflecttest.HaShiQi.eat() ——————— 獲取所有公有的構造器 —————————
因為HaShiQi類中沒有公有的構造器,所以這裡什麼都沒有打印出來。自然也有獲取到私有構造器的方法:
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // TODO Auto-generated method stub //獲取類物件,除了這份方法外還有另外兩種方法 Class clazz = HaShiQi.class; System.out.println("——————— 獲取所有公有的屬性 —————————"); Field[] fields = clazz.getFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有的屬性 —————————"); fields = clazz.getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); System.out.println(field); } System.out.println("——————— 獲取所有公有的方法 —————————"); Method[] methods = clazz.getMethods(); for(Method method : methods) { System.out.println(method); } System.out.println("——————— 獲取所有類中宣告的方法 —————————"); methods = clazz.getDeclaredMethods(); for(Method method : methods) { System.out.println(method); } System.out.println("——————— 獲取所有公有的構造器 —————————"); Constructor[] constructors = clazz.getConstructors(); for(Constructor constructor : constructors) { System.out.println(constructor); } System.out.println("——————— 獲取所有的構造器 —————————"); constructors = clazz.getDeclaredConstructors(); for(Constructor constructor : constructors) { System.out.println(constructor); } }
列印結果:
——————— 獲取所有公有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color ——————— 獲取所有的屬性 ————————— public java.lang.String com.zcz.reflecttest.HaShiQi.color private java.lang.String com.zcz.reflecttest.HaShiQi.name ——————— 獲取所有公有的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() public java.lang.String com.zcz.reflecttest.HaShiQi.toString() public void com.zcz.reflecttest.HaShiQi.eat() public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ——————— 獲取所有類中宣告的方法 ————————— public void com.zcz.reflecttest.HaShiQi.run() public java.lang.String com.zcz.reflecttest.HaShiQi.toString() private void com.zcz.reflecttest.HaShiQi.dance() public void com.zcz.reflecttest.HaShiQi.eat() ——————— 獲取所有公有的構造器 ————————— ——————— 獲取所有的構造器 ————————— private com.zcz.reflecttest.HaShiQi()
七,例項化物件
HaSHiQi類中的資訊,包括屬性,方法,構造器,我們都已經通過放射瀏覽了一遍,那麼接下來就要再使用反射例項化HaShiQi類的物件了,因為只有例項物件才能呼叫屬性和方法。
因為HaShiQi類中只顯示聲明瞭一個空參構造器,所以我們只能使用這個構造器來例項化物件。
正常情況下獲取指定引數的構造器,需要使用方法clazz.getConstructor(parameterTypes(引數類物件陣列))。但是HaShiQi的構造方法是私有的,所以使用這個方法去獲取構造器會報錯:
Constructor cons = clazz.getConstructor();
Exception in thread "main" java.lang.NoSuchMethodException: com.zcz.reflecttest.HaShiQi.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getConstructor(Class.java:1825) at com.zcz.reflecttest.LookLook.main(LookLook.java:61)
所以我們使用另外一個方法解決這個問題:
// 例項化物件 // 獲取構造器 Constructor con = clazz.getDeclaredConstructor(); // 強制設定為可以訪問 con.setAccessible(true); HaShiQi haShiQi = (HaShiQi)con.newInstance(); System.out.println("沒有做任何修改前:"+haShiQi.toString());
列印結果:
沒有做任何修改前:名字:富貴;顏色:黑色
哈哈,機智如我,怎麼能被這種問題打到,從程式碼中可以看到我們使用了構造器的newInstance方法例項化了一個HaShiQi物件。
程式碼:con.newInstance();
// 通過反射,將h作為引數,例項化代理類,返回代理類例項。 return cons.newInstance(new Object[]{h});
接下來就是訪問物件的屬性和方法瞭如果我們把所有的屬性和方法都訪問到,不久把同事的狗狗看了一遍了嘛。
八,修改屬性
// 修改狗狗的顏色 // 獲取狗狗的color屬性 Field filed = clazz.getField("color"); filed.set(haShiQi, "紅色"); System.out.println("修改狗狗的顏色:"+haShiQi.toString());
列印結果:
沒有做任何修改前:名字:富貴;顏色:黑色
修改狗狗的顏色:名字:富貴;顏色:紅色
修改成功,接著修改狗狗的名字:
// 修改狗狗的顏色 // 獲取狗狗的color屬性 Field filed = clazz.getField("color"); filed.set(haShiQi, "紅色"); System.out.println("修改狗狗的顏色:"+haShiQi.toString()); // 修改狗狗的名字 filed = clazz.getDeclaredField("name"); // 強制設定為可以訪問 filed.setAccessible(true); filed.set(haShiQi, "驚喜"); System.out.println("修改狗狗的名字:"+haShiQi.toString());
列印結果:
沒有做任何修改前:名字:富貴;顏色:黑色
修改狗狗的顏色:名字:富貴;顏色:紅色
修改狗狗的名字:名字:驚喜;顏色:紅色
修改成功,但是千萬要注意是的是的getDeclaredField方法,而不是getField方法,使用getField方法會丟擲異常:Exception in thread "main" java.lang.NoSuchFieldException: name;
同時filed.setAccessible(true);也是必不可少了,否則將丟擲異常:Exception in thread "main" java.lang.IllegalAccessException: Class com.zcz.reflecttest.LookLook can not access a member of class com.zcz.reflecttest.HaShiQi with modifiers "private"
屬性修改完,我們就開始呼叫方法吧。
九,呼叫方法
//呼叫run方法 Method method = clazz.getMethod("run"); method.invoke(haShiQi, null);
列印結果:
驚喜 去跑著玩兒
呼叫成功;
程式碼:method.invoke(haShiQi, null);
// 通過反射機制,通知力巨集做事情 method.invoke(object, args);
我們繼續,eat方法和run方法一樣,都是公有的方法,這裡就不再演示了,接下來演示一下私有的dance方法:
//呼叫run方法 Method method = clazz.getMethod("run"); method.invoke(haShiQi, null); //呼叫dance方法 method = clazz.getDeclaredMethod("dance"); method.setAccessible(true); method.invoke(haShiQi, null);
列印結果:
驚喜 去跑著玩兒
驚喜 來跳一支舞
也同樣成功了,但是也要注意getDeclaredMethod方法和method.setAccessible(true);
到這裡,同事不讓我看的狗狗,我通過java的反射機制從裡到外的全部都看了一遍,不僅僅是看了一遍,我還給他的狗狗改了顏色和名字。呼叫了所有的方法。是不是很神奇,很有成就感?
十,總結
反射的基本用法已經都在上面的例項中演示到了,但是反射還是有其他的方法在這裡並沒有提到,有機會的話繼續補充。
在上面的程式碼過程中,你也許也發現了,不僅僅是有類物件,而且還有屬性,方法和構造器都是物件,屬性是java.lang.reflect.Field類的物件,方法是java.lang.reflect.Method類的物件,構造器是java.lang.reflect.Constructor類的物件。看來在java中確實是一切皆物件。
好了,來看看其他文章中對反射的解釋吧:
Java 反射機制是在執行狀態中,對於任意一個類,都能夠獲得這個類的所有屬性和方法,對於任意一個物件都能夠呼叫它的任意一個屬性和方法。這種在執行時動態的獲取資訊以及動態呼叫物件的方法的功能稱為 Java 的反射機制。
在上面的例子中,我們是不是在執行中,獲取了HaShiQi的屬性和方法(包括私有的),是不是呼叫了每一個屬性和方法(包括私有的)。
這樣一來,反射機制是不是好理解一點兒了呢,希望能幫到你。