JavaSE基礎(chǔ)之反射機制(反射Class)詳解
一:反射機制概述
1、反射機制有什么用?
通過java語言中的反射機制可以操作字節(jié)碼文件。
優(yōu)點類似于黑客。(可以讀和修改字節(jié)碼文件。)
通過反射機制可以操作代碼片段。(class文件。)
2、反射機制的相關(guān)類在哪個包下?
java.lang.reflect.*;
3、反射機制相關(guān)的重要的類有哪些?
java.lang.Class:代表整個字節(jié)碼,代表一個類型,代表整個類。
java.lang.reflect.Method:代表字節(jié)碼中的方法字節(jié)碼。代表類中的方法。
java.lang.reflect.Constructor:代表字節(jié)碼中的構(gòu)造方法字節(jié)碼。代表類中的構(gòu)造方法
java.lang.reflect.Field:代表字節(jié)碼中的屬性字節(jié)碼。代表類中的成員變量(靜態(tài)變量+實例變量)。
// java.lang.Class:(整個是一個class)
public class User{
// Field (成員變量)
int no;
// Constructor(構(gòu)造方法)
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ù)構(gòu)造方法,必須保證無參構(gòu)造存在才可以;所以一旦我們寫上了有參構(gòu)造方法,無參構(gòu)造方法也要寫上! 如果有有參構(gòu)造方法,而沒有寫無參構(gòu)造方法會出現(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í)行結(jié)果:
無參數(shù)構(gòu)造方法
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 {
// 無參構(gòu)造(不寫也行,默認會有)
public User() {
System.out.println("無參數(shù)構(gòu)造方法");
}
// 有參構(gòu)造寫了,無參構(gòu)造必須寫;不然調(diào)用newInstance()會出現(xiàn)異常
public User(String s) {
System.out.println("無參數(shù)構(gòu)造方法");
}
}
3. 通過讀配置屬性文件實例化對象
(1)通過讀配置屬性文件實例化對象,java代碼寫一遍,再不改變java源代碼的基礎(chǔ)之上,只改變配置文件,可以做到不同對象的實例化;非常之靈活。(符合OCP開閉原則:對擴展開放,對修改關(guān)閉)
(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關(guān)閉流
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í)行結(jié)果:
無參數(shù)構(gòu)造方法
com.bjpowernode.java.bean.User@4554617c
*/
// 怎么體現(xiàn)靈活性?
// 這里的代碼我們都不改變,只改變classinfo.properties配置文件
// 例如改成:className=java.util.Date
// 此時執(zhí)行的結(jié)果就變了: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í)行結(jié)果: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)境下也是沒問題的
// 假設(shè)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
// 假設(shè)有一個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. 擴展:類加載器概述
關(guān)于JDK中自帶的類加載器:(不需要掌握)
(1)什么是類加載器?
專門負責加載類的命令/工具;ClassLoader
(2)JDK中自帶了3個類加載器
- 啟動類加載器:rt.jar
- 擴展類加載器:ext/*.jar
- 應用類加載器:classpath
(3)假設(shè)有這樣一段代碼: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)先從啟動類加載器中加載,這個稱為“父”,“父”無法加載到,再從擴展類加載器中加載,這個稱為“母”。
雙親委派。如果都加載不到,才會考慮從應用類加載器中加載。直到加載到為止。
小總結(jié)
1、回顧反射機制
(1)什么是反射機制?反射機制有什么用?
反射機制:可以操作字節(jié)碼文件
作用:可以讓程序更加靈活
(2)反射機制相關(guān)的類在哪個包下?
java.lang.reflect.*;
(3)反射機制相關(guān)的主要的類?
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ù)構(gòu)造方法來實例化對象
//c代表的就是日期Date類型
Class c = Class.forName("java.util.Date");
//實例化一個Date日期類型的對象
Object obj = c.newInstance();
一定要注意:
newInstance()底層調(diào)用的是該類型的無參數(shù)構(gòu)造方法。
如果沒有這個無參數(shù)構(gòu)造方法會出現(xiàn)"實例化"異常。
(6)如果你只想讓一個類的“靜態(tài)代碼塊”執(zhí)行的話,你可以怎么做?
Class.forName("該類的類名");這樣類就加載,類加載的時候,靜態(tài)代碼塊執(zhí)行!
(7)關(guān)于路徑問題?
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結(jié)尾,但是寫的時候不能帶上.properties。
ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/test");
String value = bundle.getString(key);到此這篇關(guān)于JavaSE基礎(chǔ)之反射機制(反射Class)詳解的文章就介紹到這了,更多相關(guān)JavaSE反射機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot bean循環(huán)依賴實現(xiàn)以及源碼分析
最近在使用Springboot做項目的時候,遇到了一個循環(huán)依賴的 問題,所以下面這篇文章主要給大家介紹了關(guān)于springboot bean循環(huán)依賴實現(xiàn)以及源碼分析的相關(guān)資料,需要的朋友可以參考下2021-06-06
Spring Cloud Gateway 獲取請求體(Request Body)的多種方法
這篇文章主要介紹了Spring Cloud Gateway 獲取請求體(Request Body)的多種方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
Spring Boot實現(xiàn)動態(tài)更新任務的方法
這篇文章主要介紹了Spring Boot實現(xiàn)動態(tài)更新任務的方法,文中給出了詳細的示例代碼供大家參考學習,對大家學習使用Spring Boot動態(tài)更新任務具有一定的參考價值,需要的朋友們來一起看看吧。2017-04-04
Java中遍歷Map的多種方法示例及優(yōu)缺點總結(jié)
在java中遍歷Map有不少的方法,下面這篇文章主要給大家介紹了關(guān)于Java中遍歷Map的多種方法,以及各種方法的優(yōu)缺點總結(jié),文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-07-07

