1姥闭,反射機(jī)制
- 反射機(jī)制有什么作用
- 通過(guò)java語(yǔ)言中的反射機(jī)制可以操作字節(jié)碼文件丹鸿;有點(diǎn)類似于黑客,可以讀和修改字節(jié)碼文件棚品。
- 通過(guò)反射機(jī)制可以操作代碼片段靠欢。(class文件)
- 反射機(jī)制的相關(guān)類在哪個(gè)包下?
- java.lang.reflect
- 反射機(jī)制相關(guān)的重要類:
- java.lang.Class 代表字節(jié)碼文件 代表一個(gè)類型
- java.lang.reflect.Method 代表字節(jié)碼中的方法字節(jié)碼
- java.lang.reflect.Constructor 代表字節(jié)碼中的構(gòu)造方法字節(jié)碼
- java.lang.reflect.Field 代表字節(jié)碼中的屬性字節(jié)碼
- 獲取一個(gè)類的字節(jié)碼文件有三種方式
- 1铜跑,Class c = Class.forName("完整的類名帶包名")
- 2门怪,Class c = 引用/對(duì)象.getClass();
- 3,Class c = 任何類型.class();
代碼示例
import java.util.Date;
public class ReflectTest {
public static void main(String[] args) {
/*
第一種方式:
Class.forName();
靜態(tài)方法
方法的參數(shù)是一個(gè)字符串
字符串需要是一個(gè)完整的類名
完整類名必須帶有包名
*/
Class c1 = null;
try {
c1 = Class.forName("java.lang.String"); // c1代表String.class字節(jié)碼文件疼进,或者說(shuō)代表String類型
Class c2 = Class.forName("java.lang.System");// c2代表Date類型
Class c3 = Class.forName("java.util.Date"); // c3代表Integer類型
Class c4 = Class.forName("java.lang.Integer");// c4代表System類型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 第二種方式:java對(duì)象中都有一個(gè)方法:getClass()
// 在方法區(qū)中薪缆,同一個(gè)類型字節(jié)碼文件只有一個(gè)秧廉。
String s = "abc";
Class x = s.getClass();// x代表String.class字節(jié)碼文件伞广,或者說(shuō)代表String類型拣帽。
// 在方法區(qū)中,String類型字節(jié)碼文件只有一個(gè)嚼锄。
System.out.println(c1 == x); // true 說(shuō)明c1和x的內(nèi)存地址是一樣的
// 第三種方式:java語(yǔ)言中任何一種類型减拭,包括基本數(shù)據(jù)類型,都有.class屬性区丑。
Class z = String.class; // z代表String類型拧粪。
Class k = Date.class; // k代表Date類型。
Class f = int.class; // f代表int類型沧侥。
Class e = double.class; // e代表double類型可霎。
System.out.println(x == z); // true 說(shuō)明x和z的內(nèi)存地址是一樣的
}
}
2,通過(guò)反射機(jī)制實(shí)例化對(duì)象
- 獲取到Class能做什么
- 通過(guò)Class的newInstance方法來(lái)實(shí)例化對(duì)象宴杀。
- 注意:newInstance內(nèi)部調(diào)用的是無(wú)參構(gòu)造方法癣朗,必須保證無(wú)參構(gòu)造方法的存在。
- Class.forName()方法的執(zhí)行時(shí)發(fā)生了什么
- Class.forName()方法的執(zhí)行會(huì)導(dǎo)致類加載旺罢;類加載時(shí)旷余,靜態(tài)代碼塊執(zhí)行。
- 如果你只希望執(zhí)行一個(gè)類的靜態(tài)代碼塊扁达,其他代碼不執(zhí)行可以使用它正卧。
代碼示例
public class ReflectTest01 {
public static void main(String[] args) {
try {
// 通過(guò)反射機(jī)制獲取Class,通過(guò)Class來(lái)實(shí)例化對(duì)象
Class c = Class.forName("com.javaSE.reflects.User");
// newInstance這個(gè)方法會(huì)調(diào)用User類的無(wú)參構(gòu)造方法跪解,完成對(duì)象的創(chuàng)建炉旷。
// newInstance調(diào)用的是無(wú)參構(gòu)造方法,必須保證無(wú)參構(gòu)造方法的存在惠遏。
Object obj = c.newInstance();
System.out.println(obj);
// Class.forName()方法的執(zhí)行會(huì)導(dǎo)致類加載
Class.forName("com.javaSE.reflects.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
class User{
public User() {
System.out.println("User無(wú)參構(gòu)造方法");
}
public User(String s) {
System.out.println("User有參構(gòu)造方法");
}
}
class MyClass{
// 靜態(tài)代碼塊在類加載時(shí)執(zhí)行砾跃,并且只執(zhí)行一次。
static {
System.out.println("靜態(tài)代碼塊執(zhí)行了");
}
}
3节吮,驗(yàn)證反射機(jī)制的靈活性
java代碼寫(xiě)一遍抽高,在不改變java源代碼的情況下,可以做到不同對(duì)象的實(shí)例化透绩,非常靈活翘骂。(符合OCP開(kāi)閉原則:對(duì)擴(kuò)展開(kāi)發(fā),對(duì)修改關(guān)閉)
代碼示例
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class ReflectTest02 {
public static void main(String[] args) {
// 1帚豪,創(chuàng)建一個(gè)名為ClassName的properties配置文件碳竟,把User類放到配置文件里:className=com.javase.reflects.User
FileReader reader = null;
try {
// 通過(guò)IO流讀取ClassName.properties文件
reader = new FileReader("src/ClassName.properties");
// 創(chuàng)建屬性類對(duì)象
Properties pro = new Properties();
// 加載
pro.load(reader);
// 通過(guò)key獲取value
String cn = pro.getProperty("className");
System.out.println(cn);
// 通過(guò)反射機(jī)制實(shí)例化對(duì)象
Class c = Class.forName(cn);
Object obj = c.newInstance();
System.out.println(obj);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
if (reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4,研究一下文件路徑問(wèn)題
怎么獲取一個(gè)文件的絕對(duì)路徑狸臣,以下講解的這種方式是通用的莹桅,前提:文件需要在類路徑下。
代碼示例
public class ReflectTest03 {
public static void main(String[] args) throws Exception {
// 這種方式路徑缺點(diǎn):移植性差烛亦,在IDEA中默認(rèn)的當(dāng)前路徑是project的跟诈泼。
// 這個(gè)代碼假設(shè)離開(kāi)了IDEA懂拾,換到了其他位置,可能當(dāng)前路徑就是project的跟了铐达,這個(gè)時(shí)候路徑就無(wú)效了岖赋。
// FileReader reader = new FileReader("src/ClassName.properties");
// 下面說(shuō)一種比較通用的路徑,即使代碼位置更改了瓮孙,仍然是有用的唐断。
// 注意:使用以下通用方式的前提是:這個(gè)文件必須在類路徑下。
// 什么是類路徑杭抠?在src下的都是類路徑下脸甘。src是類的跟路徑
/*
解析以下代碼:
Thread.currentThread() 當(dāng)前線程對(duì)象。
getContextClassLoader() 是線程對(duì)象的方法偏灿,獲取當(dāng)前線程的類加載器對(duì)象斤程。
getResource() [獲取資源]這是類加載器的方法,當(dāng)前線程的類加載器默認(rèn)從類的根路徑下加載資源菩混。
*/
String className = "ClassName.properties";
// String className = "com/javaSE/reflects/ReflectTest.class";
System.out.println(className);
// 獲取絕對(duì)路徑
String path = Thread.currentThread().getContextClassLoader().
getResource(className).getPath();
// 采用以上的代碼可以拿到一個(gè)文件的絕對(duì)路徑
System.out.println(path);
// 把絕對(duì)路徑傳給流
// FileReader reader = new FileReader(path);
// 直接以流的反射返回
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties");
// --------------------------------------------------------------------------------------------------------------------------
// 創(chuàng)建屬性對(duì)象
Properties pro = new Properties();
// 加載流
pro.load(reader);
// 關(guān)閉流
reader.close();
// 通過(guò)key值獲取value
String cl = pro.getProperty("className");
System.out.println(cl);
Class c2 = Class.forName(cl);
c2.newInstance();
}
}
5忿墅,資源綁定器(ResourceBundle)
- java.util包下提供了一個(gè)資源綁定器,便于獲取屬性配置文件中的內(nèi)容
- 使用這種方式的時(shí)候沮峡,屬性配置文件xx.properties必須放到類路徑下疚脐。
代碼示例
import java.util.ResourceBundle;
public class ReflectTest04 {
public static void main(String[] args) throws Exception {
// 資源綁定器 只能綁定xx.properties文件 這個(gè)文件必須在類路徑下 文件擴(kuò)展名不用寫(xiě)
ResourceBundle bundle = ResourceBundle.getBundle("ClassName");
// 通過(guò)key值獲取value
String cl = bundle.getString("className");
System.out.println(cl);
// 獲取字節(jié)碼文件
Class c2 = Class.forName(cl);
c2.newInstance();
}
}
關(guān)于JDK中自帶的類加載器(了解)
-
什么是類加載器
- 專門負(fù)責(zé)加載類的命令/工具。ClassLoader
-
JDK中自帶了3個(gè)類加載器
- 啟動(dòng)類加載器
- 擴(kuò)展類加載器
- 應(yīng)用類加載器
-
假設(shè)有這樣一段代碼:String s = "abc";
代碼再開(kāi)始執(zhí)行之前邢疙,會(huì)將所需要類全部加載到JVM當(dāng)中棍弄,通過(guò)類加載器加載,看到以上代碼類加載器會(huì)找到Sting.class文件進(jìn)行加疟游。
-
怎么進(jìn)行加載的呼畸?
- 首先通過(guò)啟動(dòng)類加載器加載;
- 啟動(dòng)類加載器專門加載:jdk目錄下的/jre/lib/rt.jar
- rt.jar中都是JDK中最核心的類庫(kù)颁虐。
- 如果通過(guò)啟動(dòng)加載器加載不到蛮原,就會(huì)通過(guò)擴(kuò)展類加載器進(jìn)行加載;
- 擴(kuò)展類加載器專門加載:jdk目錄下的/jre/lib/ext/*.jar
- 如果通過(guò)擴(kuò)展類加載器也加載不到另绩,就會(huì)通過(guò)應(yīng)用類加載器進(jìn)行加載儒陨。
- 應(yīng)用類加載器專門加載:classpath中的jar包。(class文件)
- 首先通過(guò)啟動(dòng)類加載器加載;
-
java中為了保證類加載的安全笋籽,使用了雙親委派機(jī)制
- 優(yōu)先從啟動(dòng)類加載器中加載蹦漠,這個(gè)稱為"父";"父"無(wú)法加載到车海,再?gòu)臄U(kuò)展類加載器加載笛园,
這個(gè)稱為"母";雙親委派如果都加載不到,才會(huì)考慮從應(yīng)用類加載器中加載研铆,直到加載到為止闸度。
- 優(yōu)先從啟動(dòng)類加載器中加載蹦漠,這個(gè)稱為"父";"父"無(wú)法加載到车海,再?gòu)臄U(kuò)展類加載器加載笛园,
6,反射屬性Field
6.1蚜印,通過(guò)反射機(jī)制獲取一個(gè)對(duì)象的屬性(Filed)
Field 翻譯為字段,其實(shí)就是屬性成員
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest06 {
public static void main(String[] args) throws Exception {
// 獲取整個(gè)類
Class student = Class.forName("com.javaSE.reflects.Student");
System.out.println("完整類名:" + student.getName());
System.out.println("簡(jiǎn)寫(xiě)類名:" + student.getSimpleName());
// 獲取類中所有public修飾的Field
Field[] fields = student.getFields();
System.out.println(fields.length);// 測(cè)試數(shù)組中只有2個(gè)元素
// 取出整個(gè)Field
Field f = fields[0];
// 取出整個(gè)Field名字
String fieldName = f.getName();
System.out.println(fieldName);
System.out.println("--------------------------------------------------------------------");
// 獲取類中所有的Field
Field[] fs = student.getDeclaredFields();
System.out.println(fs.length);// 測(cè)試數(shù)組中 有5個(gè)元素
// 遍歷
for (Field field : fs){
// 獲取屬性名字
System.out.println(field.getName());
// 獲取屬性類型
Class fieldType = field.getType();
System.out.println(field.getName() + "完整類名:" + fieldType.getName() + "留量;簡(jiǎn)寫(xiě)類名:" + fieldType.getSimpleName());
// 獲取屬性修飾符列表
int mods = field.getModifiers();// 返回的修飾符是一個(gè)數(shù)字窄赋,每個(gè)數(shù)組是修飾符的代號(hào)。
// 將代號(hào)數(shù)字轉(zhuǎn)換成修飾符
String mos = Modifier.toString(mods);
System.out.println(field.getName() + "修飾符代號(hào):" + mods + "楼熄;修飾符是:" + mos);
}
System.out.println("-------------通過(guò)反射機(jī)制忆绰,反編譯一個(gè)類的屬性---------------------------------------------");
// 通過(guò)反射機(jī)制,反編譯一個(gè)類的屬性Field
fbyStudent();
}
/**
* 通過(guò)反射機(jī)制可岂,反編譯一個(gè)類的屬性
* @throws ClassNotFoundException
*/
static void fbyStudent() throws ClassNotFoundException {
// 獲取整個(gè)類
Class student = Class.forName("com.javaSE.reflects.Student");
// Class student = Class.forName("java.lang.Integer");
// 創(chuàng)建一個(gè)拼接字符串對(duì)象
StringBuilder sr = new StringBuilder();
// 開(kāi)始拼寫(xiě)
sr.append(Modifier.toString(student.getModifiers()) + " class " + student.getSimpleName() + "{\n");
Field[] fds = student.getDeclaredFields();
for (Field field : fds){
sr.append("\t");
sr.append(Modifier.toString(field.getModifiers()));
sr.append(" ");
sr.append(field.getType().getSimpleName());
sr.append(" ");
sr.append(field.getName());
sr.append(";\n");
}
sr.append("}");
System.out.println(sr);
}
}
// 反射屬性Field
class Student{
// Field 翻譯為字段错敢,其實(shí)就是屬性成員
// 4個(gè)Field,分別采用不同的訪問(wèn)控制權(quán)限修飾符
public int no;
private String name;
protected int age;
boolean sex;
public static final double MIN_PI = 3.1415926;
}
6.2,通過(guò)反射機(jī)制操作一個(gè)對(duì)象的屬性(Field)
給屬性賦值set, 獲取屬性的值get缕粹。
public class ReflectTest07 {
public static void main(String[] args) throws Exception {
// 使用反射機(jī)制獲取/修改一個(gè)對(duì)象屬性的值
Class cStudent = Class.forName("com.javaSE.reflects.Student");
Object obj = cStudent.newInstance(); // obj就是Student對(duì)象稚茅。(底層調(diào)用無(wú)參構(gòu)造方法)
// 獲取no屬性 (根據(jù)屬性的名稱獲取Field)
Field noField = cStudent.getField("no");
// 給obj對(duì)象的no屬性賦值
noField.set(obj,1234);
// 讀取no屬性的值
System.out.println(noField.get(obj));
// 獲取私有屬性name
Field nameField = cStudent.getDeclaredField("name");
// 打破封裝
// 這樣設(shè)置之后,在外部也可以訪問(wèn)private的
nameField.setAccessible(true);
// 給name屬性賦值
nameField.set(obj,"kitty");
// 獲取name屬性的值
System.out.println(nameField.get(obj));
}
}