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

