java的反射機制(重要)
1,反射的概念
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
java程序的加載過程:源文件 .java --- 經過編譯(javac.exe)--- 得到一個或多個 .class文件 --- 再運行(java.exe) --- 就會對需要用到的類進行加載(通過JVM的類加載器)--- 把 .class 加載到內存後 JVM會自動根據.class給java.lang.Class實例化一個Class對象,該對象和.class一一對應(即一個類就一個Class對象)--- 然後再根據 Class對象創建該類的對象。
因此:java.lang.Class就是反射的源頭。
1)java.lang.Class的實例表示正在運行的 Java 應用程序中的類和接口。也就是jvm中有N多的實例每個類都有該Class對象。(包括基本數據類型)
2)java.lang.Class沒有公共構造方法。Class
對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的defineClass
方法自動構造的。也就是這不需要我們自己去處理創建,JVM已經幫我們創建好了。
2,反射的優缺點
靜態編譯:在編譯時確定類型,綁定對象,即通過。
動態編譯:運行時確定類型,綁定對象。動態編譯最大限度發揮了Java的靈活性,體現了多態的應用,有以降低類之間的藕合性。
反射機制的優點:可以實現動態創建對象和編譯,體現出很大的靈活性(特別是在J2EE的開發中它的靈活性就表現的十分明顯)。通過反射機制我們可以獲得類的各種內容,進行反編譯。
反射機制的缺點:對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麽並且讓它滿足我們的要求。這類操作總是慢於直接執行相同的操作。
3,反射的用法
3.1,獲取java.lang.Class對象的三種方法
- 通過運行時類的對象,調用其getClass()方法
- 調用運行時類的.class屬性
- 調用Class的靜態方法forName(String className)。此方法報ClassNotFoundException
public class Student { }
public class Reflect { public static void main(String[] args) { // (1)通過運行時類的對象,調用其getClass()方法 Student stu1 = new Student();// 這一new 產生一個Student對象,一個Class對象。 Class stuClass = stu1.getClass();// 獲取Class對象 System.out.println(stuClass.getName()); // (2)調用運行時類的.class屬性 Class stuClass2 = Student.class; System.out.println(stuClass == stuClass2);// 判斷第一種方式獲取的Class對象和第二種方式獲取的是否是同一個 // (3)調用Class的靜態方法forName(String className)。此方法報ClassNotFoundException try { Class stuClass3 = Class.forName("com.jp.Student");// 註意此字符串必須是真實路徑 System.out.println(stuClass3 == stuClass2);// 判斷三種方式是否獲取的是同一個Class對象 } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
說明:
1)在運行期間,一個類,只有一個Class對象產生。上面三種方法都是去內存獲取這個Student這個類(即java.lang.Class的一個實例)
2)如果Student類寫錯了,第一二種編譯時就報錯(編譯器會對java源碼進行詞法分析、語法分析、語義分析等步驟才編譯為字節碼); 第三種編譯時會通過,但是運行時會報錯;這就說明了第三種是動態的編譯。
為了說明 2)來做個試驗
第一步:把Student類改成錯的
public class Student { int a = gg;//很明顯錯的 }
第二步:分別單獨運行Reflect類裏的三種獲取Class實例對象的方法
運行前涉及到的兩個類
單獨運行第一種方法的結果:編譯錯誤。說明也對Student.java進行了編譯,如果Student類沒有錯誤也會生成.class(單獨運行第二種方法和第一種結果一樣)
單獨運行第三種方法的結果:編譯成功,說明編譯時並不會去編譯Class.forName()裏的類
3.2,有了java.lang.Class實例(即除Class類的其他類的運行時類)後可以做什麽
3.2.1 應用一:可以創建對應的運行時類的對象
//方法一:Class的newInstanc()方法 @Test public void test1() throws Exception{ Class clazz = Class.forName("com.jp.Animal");//拿到Class實例(即運行時類) Object obj = clazz.newInstance(); Animal a = (Animal)obj; System.out.println(a); }
//方法二:Class的getDeclaredConstructor()方法,拿到指定的構造器,再用構造器的newInstance()方法 @Test public void test2() throws Exception{ Class clazz = Animal.class;//拿到Class實例(即運行時類) Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);//這裏和上面說的一致,對象都有Class實例,包括基本數據類型、字符串等等 cons.setAccessible(true); Animal a = (Animal)cons.newInstance("Tom",10); System.out.println(a); }
3.2.2 應用二:調用對應的運行時類中指定的結構(某個指定的屬性、方法、構造器等)
1)獲取構造方法
批量的方法:
public Constructor[] getDeclaredConstructors():獲取所有的構造方法(包括私有、受保護、默認、公有)
獲取單個的方法,並調用:
public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"可以是私有的,或受保護、默認、公有;
2)獲取成員變量
批量的
Field[] getFields():獲取所有的"公有字段"
Field[] getDeclaredFields():獲取所有字段,包括:私有、受保護、默認、公有;
獲取單個的
public Field getField(String fieldName):獲取某個"公有的"字段;
public Field getDeclaredField(String fieldName):獲取某個字段(可以是私有的)
3)獲取方法
批量的:
public Method[] getMethods():獲取所有"公有方法";(包含了父類的方法也包含Object類) public Method[] getDeclaredMethods():獲取所有的成員方法,包括私有的(不包括繼承的) 獲取單個的: public Method getMethod(String name,Class<?>... parameterTypes)- 參數:
- name : 方法名;
- Class ... : 形參的Class類型對象
public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
調用方法: Method --> public Object invoke(Object obj,Object... args):- 參數說明:
- obj : 要調用方法的對象;
- args:調用方式時所傳遞的實參
Method m = stuClass.getMethod("show1", String.class);//拿到運行時類Method的實例對象 System.out.println(m); //實例化一個Student對象 Object obj = stuClass.getConstructor().newInstance(); Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(反射得到),一個是實參(m方法需要的參數)
如果獲得的Method的實例對象 是 靜態方法的,則運行該靜態方法時,.invoke(null,20),對象類型可以寫null。
4,典型的應用
1)代理設計模式中的動態代理
動態代理:在程序運行時,根據被代理類及其實現的接口,動態的創建一個代理類。當調用代理類的實現的抽象方法時,就發起對被代理類同樣 方法的調用。
2)通過反射運行配置文件內容(這就是IOC容器的基本原理)
Student類:
public class Student { public static void show(){ System.out.println("is show()"); } }
配置文件以txt文件為例子(pro.txt):
className = Student
methodName = show
測試類(應用程序):
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; /* * 我們利用反射和配置文件,可以使:應用程序更新時,對源碼無需進行任何修改 * 我們只需要將新類發送給客戶端,並修改配置文件即可 */ public class Demo { public static void main(String[] args) throws Exception { // 通過反射獲取Class對象 Class stuClass = Class.forName(getValue("className"));// "Student" // 2獲取show()方法 Method m = stuClass.getMethod(getValue("methodName"));// show // 3.調用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } // 此方法接收一個key,在配置文件中獲取相應的value public static String getValue(String key) throws IOException { Properties pro = new Properties();// 獲取配置文件的對象 FileReader in = new FileReader("pro.txt");// 獲取輸入流 pro.load(in);// 將流加載到配置文件對象中 in.close(); return pro.getProperty(key);// 返回根據key獲取的value值 } }
控制臺輸出
需求:
當我們升級這個系統時,不要Student類,而需要新寫一個Student2的類時,這時只需要更改pro.txt的文件內容就可以了。代碼就一點不用改動
要替換的student2類:
配置文件更改為:
控制臺輸出: is show2();3)通過反射越過泛型檢查
泛型用在編譯期,編譯過後泛型擦除(消失掉)。所以是可以通過反射越過泛型檢查的
package fanXing; import java.lang.reflect.Method; import java.util.ArrayList; /* * 通過反射越過泛型檢查 * * 例如:有一個String泛型的集合,怎樣能向這個集合中添加一個Integer類型的值? */ public class FanXing { public static void main(String[] args) throws Exception { ArrayList<String> strList = new ArrayList<>(); strList.add("aaa"); strList.add("bbb"); // strList.add(100);//如果直接在這添加 100 的話 編譯就報錯了。 // 獲取ArrayList的Class對象,反向的調用add()方法,添加數據 Class listClass = strList.getClass(); // 得到 strList 對象的字節碼 對象 // 獲取add()方法 Method m = listClass.getMethod("add", Object.class); // 調用add()方法 m.invoke(strList, 100); // 遍歷集合 for (Object obj : strList) { System.out.println(obj); } } }
參考:
https://blog.csdn.net/sinat_38259539/article/details/71799078
https://blog.csdn.net/fuzhongmin05/article/details/61614873
java的反射機制(重要)