JavaSE基礎之反射機制(反射Class)詳解
一:反射機制概述
1、反射機制有什么用?
通過java語言中的反射機制可以操作字節(jié)碼文件。
優(yōu)點類似于黑客。(可以讀和修改字節(jié)碼文件。)
通過反射機制可以操作代碼片段。(class文件。)
2、反射機制的相關類在哪個包下?
java.lang.reflect.*;
3、反射機制相關的重要的類有哪些?
java.lang.Class:代表整個字節(jié)碼,代表一個類型,代表整個類。
java.lang.reflect.Method:代表字節(jié)碼中的方法字節(jié)碼。代表類中的方法。
java.lang.reflect.Constructor:代表字節(jié)碼中的構造方法字節(jié)碼。代表類中的構造方法
java.lang.reflect.Field:代表字節(jié)碼中的屬性字節(jié)碼。代表類中的成員變量(靜態(tài)變量+實例變量)。
// java.lang.Class:(整個是一個class) public class User{ // Field (成員變量) int no; // Constructor(構造方法) public User(){ } public User(int no){ this.no = no; } // Method(方法) public void setNo(int no){ this.no = no; } public int getNo(){ return no; } }
二:反射Class
1. 獲取Class的三種方式
要操作一個類的字節(jié)碼,需要首先獲取到這個類的字節(jié)碼,怎么獲取java.lang.Class實例?
三種方式:
第一種:Class c = Class.forName("完整類名帶包名");
1、靜態(tài)方法
2、方法的參數(shù)是一個字符串。
3、字符串需要的是一個完整類名。
4、完整類名必須帶有包名。java.lang包也不能省略。
第二種:Class c = 對象(引用).getClass();
第三種:Class c = 任何類型.class;
package com.bjpowernode.java.reflect; import java.util.Date; public class ReflectTest01 { public static void main(String[] args) { // 第一種方式:Class.forName() Class c1 = null; Class c2 = null; try { // c1代表String.class文件,或者說c1代表String類型。 c1 = Class.forName("java.lang.String"); // c2代表Date類型 c2 = Class.forName("java.util.Date"); // c3代表Integer類型 Class c3 = Class.forName("java.lang.Integer"); // c4代表System類型 Class c4 = Class.forName("java.lang.System"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 第二種方式:對象.getClass() // java中任何一個對象都有一個方法:getClass() String s = "abc"; // x代表String.class字節(jié)碼文件;x代表String類型 Class x = s.getClass(); // true(==判斷的是對象的內(nèi)存地址) System.out.println(x == c1); Date time = new Date(); Class y = time.getClass(); // true (c2和y兩個變量中保存的內(nèi)存地址都是一樣的,都指向方法區(qū)中的字節(jié)碼文件) System.out.println(c2 == y); // 第三種方式,java語言中任何一種類型,包括基本數(shù)據(jù)類型,它都有.class屬性。 // z代表String類型 Class z = String.class; // k代表Date類型 Class k = Date.class; // f代表int類型 Class f = int.class; // e代表double類型 Class e = double.class; System.out.println(c1 == x && x == z); // true } }
2. 通過反射實例化(創(chuàng)建)對象
(1)獲取到Class,通過Class的newInstance()方法來實例化(創(chuàng)建)對象。
(2)newInstance()方法內(nèi)部實際上調(diào)用了無參數(shù)構造方法,必須保證無參構造存在才可以;所以一旦我們寫上了有參構造方法,無參構造方法也要寫上! 如果有有參構造方法,而沒有寫無參構造方法會出現(xiàn)異java.lang.InstantiationException 實例化異常
package com.bjpowernode.java.reflect; import com.bjpowernode.java.bean.User; public class ReflectTest02 { public static void main(String[] args) { // 第一種方法創(chuàng)建對象:不使用反射機制 User user = new User(); System.out.println(user); // 第二種方法創(chuàng)建對象:以反射機制的方式創(chuàng)建對象。(這種方式比較靈活) try { // 通過反射機制,獲取Class,通過Class來實例化(創(chuàng)建)對象 Class c = Class.forName("com.bjpowernode.java.bean.User"); Object obj = c.newInstance(); System.out.println(obj); /* 執(zhí)行結果: 無參數(shù)構造方法 com.bjpowernode.java.bean.User@4554617c */ } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } }
package com.bjpowernode.java.bean; public class User { // 無參構造(不寫也行,默認會有) public User() { System.out.println("無參數(shù)構造方法"); } // 有參構造寫了,無參構造必須寫;不然調(diào)用newInstance()會出現(xiàn)異常 public User(String s) { System.out.println("無參數(shù)構造方法"); } }
3. 通過讀配置屬性文件實例化對象
(1)通過讀配置屬性文件實例化對象,java代碼寫一遍,再不改變java源代碼的基礎之上,只改變配置文件,可以做到不同對象的實例化;非常之靈活。(符合OCP開閉原則:對擴展開放,對修改關閉)
(2)配置文件寫好,命名為xxx.properties,然后使用IO流+Properties
(3)后期我們要學習的是高級框架,而工作過程中,也都是使用高級框架,
包括: ssh ssm
- Spring SpringMVC MyBatis
- Spring Struts Hibernate
- ...
這些高級框架底層實現(xiàn)原理:都采用了反射機制。所以反射機制很重要的;學會了反射機制有利于我們理解剖析框架底層的源代碼。
package com.bjpowernode.java.reflect; import java.io.FileReader; import java.util.Properties; public class ReflectTest03 { public static void main(String[] args) throws Exception { // IO流+Properties集合 // 通過IO流讀classinfo.properties配置文件 // 配置文件內(nèi)容是:className=com.bjpowernode.java.bean.User FileReader reader = new FileReader("day08\\classinfo.properties"); // 創(chuàng)建屬性類對象Map,properties的key和value都是String Properties pro = new Properties(); // 加載 pro.load(reader); // reader關閉流 reader.close(); // 通過key獲取value String s = pro.getProperty("className"); //System.out.println(s); // com.bjpowernode.java.bean.User // 最后在通過反射機制實例化對象 Class c = Class.forName(s); Object obj = c.newInstance(); System.out.println(obj); /* 執(zhí)行結果: 無參數(shù)構造方法 com.bjpowernode.java.bean.User@4554617c */ // 怎么體現(xiàn)靈活性? // 這里的代碼我們都不改變,只改變classinfo.properties配置文件 // 例如改成:className=java.util.Date // 此時執(zhí)行的結果就變了:Wed Aug 03 15:40:02 CST 2022 } }
4. 只讓靜態(tài)代碼塊執(zhí)行
Class.forName()執(zhí)行發(fā)生了什么
(1)Class.forName("完整類名");這個方法的執(zhí)行會導致類加載,類加載時,靜態(tài)代碼塊執(zhí)行。如果你只是希望一個類的靜態(tài)代碼塊執(zhí)行,其它代碼一律不執(zhí)行,使用Class.forName()
package com.bjpowernode.java.reflect; public class ReflectTest04 { public static void main(String[] args) { try { // Class.forName()這個方法的執(zhí)行會導致:類加載。 // 類加載,靜態(tài)代碼塊就會執(zhí)行 Class.forName("com.bjpowernode.java.reflect.MyClass"); // 執(zhí)行結果:MyClass類的靜態(tài)代碼塊執(zhí)行了! } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class MyClass{ // 靜態(tài)代碼塊在類加載時執(zhí)行,并且只執(zhí)行一次 static{ System.out.println("MyClass類的靜態(tài)代碼塊執(zhí)行了!"); } }
5. 獲取類路徑下文件的絕對路徑
(1)怎么獲取一個文件的絕對路徑。以下講解的這種方式是通用的。但前提是:文件需要在類路徑(src)下才能用這種方式。
(2) 例如:
String path = Thread.currentThread().getContextClassLoader() .getResource("User.properties").getPath();
- Thread.currentThread() 當前線程對象
- getContextClassLoader() 是線程對象的方法,可以獲取到當前線程的類加載器對象。
- getResource() 【獲取資源】這是類加載器對象的方法,當前線程的類加載器默認從類的根路徑下加載資源。
- getPath() 獲取路徑
package com.bjpowernode.java.reflect; import java.io.FileReader; // 研究一下文件路徑的問題 public class AboutPath { public static void main(String[] args) throws Exception { // 我們寫成下面這種路徑形式,只能在IDEA工具中才能找到,不夠通用! FileReader reader = new FileReader("day08\\classinfo.properties"); // 通用的一種方式: // 注意:使用以下通用方式的前提是:這個文件必須在類路徑下。 // 什么類路徑下?方式在src下的都是類路徑下?!緎rc是類的根路徑】 //Thread.currentThread() 當前線程對象 //getContextClassLoader() 是線程對象的方法,可以獲取到當前線程的類加載器對象。 //getResource() 【獲取資源】這是類加載器對象的方法,當前線程的類加載器默認從類的根路徑下加載資源。 // 寫成下面這種形式,放到Linux環(huán)境下也是沒問題的 // 假設classinfo.properties剛好在src下 String path = Thread.currentThread().getContextClassLoader() .getResource("classinfo.properties").getPath(); // 拿到絕對路徑 System.out.println(path); // C:/Users/86177/IdeaProjects/JavaSe1/out/production/day08/classinfo.properties // 假設有一個example文件沒有直接在src下面,而是bean下面(com/bjpowernode/java/bean/example) String path2 = Thread.currentThread().getContextClassLoader() .getResource("com/bjpowernode/java/bean/example").getPath(); // 獲取絕對路徑 System.out.println(path2); // C:/Users/86177/IdeaProjects/JavaSe1/out/production/day08/com/bjpowernode/java/bean/example } }
這樣我們就可以修改原來的代碼,得到更加通用的方式!
第一種:先通過相對路徑(這里的相對路徑前提:一定是在src下的才可以;在模塊下的就不行)獲取絕對路徑,然后創(chuàng)建流:
// 1.得到相對路徑 String path =Thread.currentThread().getContextClassLoader().getResource("相對路徑").getPath(); // 2.創(chuàng)建流 FileReader reader = new FileReader(path);
第二種方式:直接返回一個流(InputStream)
InputStream reader = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/bjpowernode/java/bean/example");
注意:這兩種方式還是還是使用IO流+properties集合的方式,使用絕對路徑而不是相對路徑更加的通用:
第一種方式先得到絕對路徑,返回String,然后在創(chuàng)建IO流
第二種方式直接返回的就是一個流InputStream
package com.bjpowernode.java.reflect; import java.io.FileReader; import java.io.InputStream; import java.util.Properties; public class ReflectTest05 { public static void main(String[] args) throws Exception { // 第一種方式:先拿到絕對路徑,然后創(chuàng)建流 //還是以example為例(className=java.util.Date),先拿到絕對路徑 String path = Thread.currentThread().getContextClassLoader() .getResource("com/bjpowernode/java/bean/example").getPath(); FileReader reader = new FileReader(path); // 第二種方式:直接返回一個流(InputStream) InputStream reader = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/bjpowernode/java/bean/example"); // 創(chuàng)建Map集合對象 Properties pro = new Properties(); pro.load(reader); reader.close(); // 通過key獲取value String className = pro.getProperty("className"); // 創(chuàng)建對象 Class c = Class.forName(className); Object obj= c.newInstance(); System.out.println(obj); // Wed Aug 03 17:00:36 CST 2022 } }
第三種方式:利用資源綁定器(常用)
(1)前兩種方式都需要創(chuàng)建一個流,而是用資源綁定器就不需要了!
(2)java.util包下提供了一個資源綁定器,便于獲取屬性配置文件中的內(nèi)容。
(3)使用這種方式的時候,屬性配置文件xxx.properties必須放到類路徑下。
資源綁定器,只能綁定xxx.properties文件。并且這個文件必須在類路徑下。文件擴展名也必須是properties
(4)并且在寫路徑的時候,路徑后面的擴展名.properties不能寫。
ResourceBundle boudle = ResourceBundle.getBundle("classinfo"); String className = boudle.getString("className"); package com.bjpowernode.java.reflect; import java.util.ResourceBundle; public class ResourceBundleTest { public static void main(String[] args) throws Exception { // 例如:classinfo.properties(className=java.util.Date) ResourceBundle boudle = ResourceBundle.getBundle("classinfo"); // 通過key獲取value String className = boudle.getString("className"); //System.out.println(className); // java.util.Date // 實例化對象 Class c = Class.forName(className); Object obj = c.newInstance(); System.out.println(obj); // Wed Aug 03 19:31:20 CST 2022 } }
6. 擴展:類加載器概述
關于JDK中自帶的類加載器:(不需要掌握)
(1)什么是類加載器?
專門負責加載類的命令/工具;ClassLoader
(2)JDK中自帶了3個類加載器
- 啟動類加載器:rt.jar
- 擴展類加載器:ext/*.jar
- 應用類加載器:classpath
(3)假設有這樣一段代碼:String s = "abc";
代碼在開始執(zhí)行之前,會將所需要類全部加載到JVM當中。通過類加載器加載,看到以上代碼類加載器會找String.class文件,找到就加載,那么是怎么進行加載的呢?
首先通過“啟動類加載器”加載
注意:啟動類加載器專門加載:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jarrt.jar中都是JDK最核心的類庫。
如果通過“啟動類加載器”加載不到的時候,然后會通過"擴展類加載器"加載
注意:擴展類加載器專門加載:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\*.jar
如果“擴展類加載器”沒有加載到,那么會通過“應用類加載器”加載
注意:應用類加載器專門加載:classpath中的類。
(4)java中為了保證類加載的安全,使用了雙親委派機制。
優(yōu)先從啟動類加載器中加載,這個稱為“父”,“父”無法加載到,再從擴展類加載器中加載,這個稱為“母”。
雙親委派。如果都加載不到,才會考慮從應用類加載器中加載。直到加載到為止。
小總結
1、回顧反射機制
(1)什么是反射機制?反射機制有什么用?
反射機制:可以操作字節(jié)碼文件
作用:可以讓程序更加靈活
(2)反射機制相關的類在哪個包下?
java.lang.reflect.*;
(3)反射機制相關的主要的類?
java.lang.Class
java.lang.reflect.Method;
java.lang.reflect.Constructor;
java.lang.reflect.Field;
(4)在java中獲取Class的三種方式?
第一種:
Class c = Class.forName("完整類名");
第二種:
Class c = 對象.getClass();
第三種:
Class c = int.class;
(5)獲取了Class之后,可以調(diào)用無參數(shù)構造方法來實例化對象
//c代表的就是日期Date類型 Class c = Class.forName("java.util.Date"); //實例化一個Date日期類型的對象 Object obj = c.newInstance();
一定要注意:
newInstance()底層調(diào)用的是該類型的無參數(shù)構造方法。
如果沒有這個無參數(shù)構造方法會出現(xiàn)"實例化"異常。
(6)如果你只想讓一個類的“靜態(tài)代碼塊”執(zhí)行的話,你可以怎么做?
Class.forName("該類的類名");這樣類就加載,類加載的時候,靜態(tài)代碼塊執(zhí)行!
(7)關于路徑問題?
String path = Thread.currentThread().getContextClassLoader() .getResource("寫相對路徑,但是這個相對路徑從src出發(fā)開始找").getPath(); String path = Thread.currentThread().getContextClassLoader() .getResource("abc").getPath(); //必須保證src下有abc文件。 String path = Thread.currentThread().getContextClassLoader() .getResource("a/db").getPath(); //必須保證src下有a目錄,a目錄下有db文件。
這種方式是為了獲取一個文件的絕對路徑。(通用方式,不會受到環(huán)境移植的影響)
但是該文件要求放在類路徑下,換句話說:也就是放到src下面。src下是類的根路徑。
// 直接以流的形式返回: InputStream in = Thread.currentThread().getContextClassLoader() .getResourceAsStream("com/bjpowernode/test.properties");
(8)IO流 + Properties集合,怎么快速綁定屬性資源文件?
// 第一:第一這個文件必須在類路徑(src)下 // 第二:這個文件必須是以.properties結尾,但是寫的時候不能帶上.properties。 ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/test"); String value = bundle.getString(key);
到此這篇關于JavaSE基礎之反射機制(反射Class)詳解的文章就介紹到這了,更多相關JavaSE反射機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot bean循環(huán)依賴實現(xiàn)以及源碼分析
最近在使用Springboot做項目的時候,遇到了一個循環(huán)依賴的 問題,所以下面這篇文章主要給大家介紹了關于springboot bean循環(huán)依賴實現(xiàn)以及源碼分析的相關資料,需要的朋友可以參考下2021-06-06Spring Cloud Gateway 獲取請求體(Request Body)的多種方法
這篇文章主要介紹了Spring Cloud Gateway 獲取請求體(Request Body)的多種方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01Spring Boot實現(xiàn)動態(tài)更新任務的方法
這篇文章主要介紹了Spring Boot實現(xiàn)動態(tài)更新任務的方法,文中給出了詳細的示例代碼供大家參考學習,對大家學習使用Spring Boot動態(tài)更新任務具有一定的參考價值,需要的朋友們來一起看看吧。2017-04-04