今日內容介紹
- 類加載器
- 反射構造方法
- 反射成員變量
- 反射成員方法
- 反射配置文件運行類中的方法
1 類加載器
1.1. 類的加載
當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,連接,初始化三步來實現對這個類進行初始化。
* a 加載
* 就是指將class文件讀入內存,併爲之創建一個Class對象。
* 任何類被使用時系統都會建立一個Class對象
* b 連接
* 驗證 是否有正確的內部結構,並和其他類協調一致
* 準備 負責爲類的靜態成員分配內存,並設置默認初始化值
* 解析 將類的二進制數據中的符號引用替換爲直接引用
* c 初始化
* 就是我們以前講過的初始化步驟(new 對象)
* 注:簡單的說就是:把.class文件加載到內存裏,並把這個.class文件封裝成一個Class類型的對象。
1.2. 類的加載時機
* a. 創建類的實例
* b. 類的靜態變量,或者爲靜態變量賦值
* c. 類的靜態方法
* d. 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象
* e. 初始化某個類的子類
* f. 直接使用java.exe命令來運行某個主類
1.3 類加載器
負責將.class文件加載到內在中,併爲之生成對應的Class對象。
* a. Bootstrap ClassLoader 根類加載器
* 也被稱爲引導類加載器,負責Java核心類的加載
* 比如System,String等。在JDK中JRE的lib目錄下rt.jar文件中
* b. Extension ClassLoader 擴展類加載器
* 負責JRE的擴展目錄中jar包的加載。
* 在JDK中JRE的lib目錄下ext目錄
* c. System ClassLoader 系統類加載器
* 負責在JVM啓動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑。
* 我們用的是System ClassLoader 系統類加載器
1.4 類加載器的組成
a. Bootstrap ClassLoader 根類加載器
也被稱爲引導類加載器,負責Java核心類的加載
比如System,String等。在JDK中JRE的lib目錄下rt.jar文件中
b. Extension ClassLoader 擴展類加載器
負責JRE的擴展目錄中jar包的加載。
在JDK中JRE的lib目錄下ext目錄
c. System ClassLoader 系統類加載器
負責在JVM啓動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑。
2. 反射
* a. 反射定義
JAVA反射機制是在運行狀態中,
對於任意一個類,都能夠知道這個類的所有屬性和方法;
對於任意一個對象,都能夠調用它的任意一個方法和屬性;
這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
* b.反射技術
條件:運行狀態
已知:一個類或一個對象(根本是已知.class文件)
結果:得到這個類或對象的所有方法和屬性
* 注: 要想解剖一個類,必須先要獲取到該類的字節碼文件對象。
而解剖使用的就是Class類中的方法.所以先要獲取到每一個字節碼文件對應的Class類型的對象。
package ReflectDemo;
public class Person {
private String name;
private int age;
// static {
// System.out.println("靜態代碼塊");
// }
/**
*
*/
public Person() {
super();
}
/**
* @param name
* @param age
*/
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
private Person(int age,String name) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat() {
System.out.println("人吃飯!");
}
public void sleep(String s,int i,double d) {
System.out.println("人在睡覺" + s + "..." + i + ".." + d + ".");
}
}
2.1 通過方法獲取Class類
方式一: 通過Object類中的getClass()方法
Person person = new Person();
Class clazz = person.getClass();
方式二: 通過 類名.class 獲取到字節碼文件對象(任意數據類型都具備一個class靜態屬性,看上去要比第一種方式簡單)。
Class clazz = Person.class;
方式三: 通過Class類中的方法(將類名作爲字符串傳遞給Class類中的靜態方法forName即可)。
Class c3 = Class.forName("Person");
注:第三種和前兩種的區別是:
前兩種你必須明確Person類型.
後面是指定這種類型的字符串就行.這種擴展更強.我不需要知道你的類.我只提供字符串,按照配置文件加載就可以了
package ReflectDemo;
public class Demo {
/*
* 獲取一個類的class 文件對象 的三種方式
* 1. 對象獲取
* 2. 類名獲取
* 3. class類的靜態方法獲取
* 調用的對象具有唯一性
* */
public static void main(String[] args) throws ClassNotFoundException {
// 1. 對象獲取
Person p = new Person();
// 調用Person類的父類方法getClass
Class c1 = p.getClass();
System.out.println(c1);
// 2. 類名獲取
// 每個類型,都會賦予這個類型一個靜態的屬性,屬性名字爲class
Class c2 = Person.class;
System.out.println(c2);
//System.out.println(c1==c2); //True 原因在於調用的都是同一個person類
//System.out.println(c1.equals(c2)); //True
// 3. Class類的靜態方法獲取 forName(字符串的類名)
Class c3 = Class.forName("ReflectDemo.Person");
System.out.println(c3);
//System.out.println(c1==c3); //True 原因在於調用的都是同一個person類
//System.out.println(c1.equals(c3)); //True
}
}
2.2 通過反射獲取構造方法並使用:構造方法使用類Constructor表示
package ReflectDemo;
import java.lang.reflect.Constructor;
/*
* 通過反射獲取class文件中的構造方法,運行構造方法
* 運行構造方法,創建對象
* 獲取class文件對象
* 從class文件對象中,獲取需要的成員
*
* Constructor 描述構造方法對象類
* */
public class ReflectConstructor {
public static void main(String[] args) throws Exception {
Class c = Class.forName("ReflectDemo.Person");
// 使用class文件對象,獲取類中的構造方法
// 1. Constructor [] getComstructors() 獲取class文件對象中所有公共的構造方法
Constructor [] cons = c.getConstructors();
for (Constructor constructor : cons) {
System.out.println(constructor);
}
// 2. 獲取指定的構造方法(空參數的構造方法)
Constructor con1 = c.getConstructor();
// System.out.println("con = " + con );
// 運行空參構造方法,Constructor類方法newInstance()運行獲取到的構造方法
Object obj1 = con1.newInstance();
System.out.println(obj1.toString());
// 想要調用person的特有方法,需要向下
Person p = (Person)obj1;
p.eat();
// 3. 獲取指定的構造方法(有參數的構造方法)
// 傳遞要獲取的構造方法的參數列表
Constructor con2 = c.getConstructor(String.class,int.class);
System.out.println(con2);
// 運行構造方法,傳遞的實際參數
Object obj2 = con2.newInstance("張燦",20);
System.out.println(obj2.toString());
/*
* 4. 反射獲取構造方法並運行,快捷方式
* 要求: 被反射的類,必須有空參構造方法;構造方法權限必須爲public
* class類中定義方法, T newInstance() 直接創建被反射類的對象實例
*/
Object obj3 = c.newInstance();
System.out.println(obj3);
}
}
通過反射方式,獲取私有構造方法,暴力反射:getDeclaredConstructor()
import java.lang.reflect.Constructor;
/*
* 反射獲取私有的構造方法運行:不推薦,會破壞程序的封裝性,安全性
* getDeclaredConstructor()
* setAccessible()
* 暴力反射
* */
public class ReflectPrivateConstructor {
public static void main(String[] args) throws Exception {
Class c = Class.forName("ReflectDemo.Person");
Constructor con = c.getDeclaredConstructor(int.class,String.class);
con.setAccessible(true);
Object obj = con.newInstance(18,"里斯");
System.out.println(obj);
}
}
2.3 通過反射方式,獲取成員變量並改值,成員變量用Field表示
package ReflectDemo;
import java.lang.reflect.Field;
public class ReflectField {
public static void main(String[] args) throws Exception {
Class c = Class.forName("ReflectDemo.Person");
Object obj = c.newInstance();
// // 1. 獲取所有的成員變量 getFields()
// Field[] fields = c.getDeclaredFields();
// for (Field field : fields) {
// System.out.println(field);
// }
// 2. 獲取指定的成員變量 getField(傳遞字符串類型的變量名)
Field field = c.getDeclaredField("name");
System.out.println(field);
// 3. 修改成員變量的值 void set(Object obj,Object value)
// Object obj 必須有對象的支持,Object value 修改後的值
// private的成員變量不能被修改
field.set(obj, "王武");
System.out.println(obj);
}
}
2.4 反射獲取空參數成員方法並運行,成員方法有Method表示
package ReflectDemo;
import java.lang.reflect.Method;
/*
* 反射獲取成員方法並運行
* Method類是描述成員方法的對象
* getMethod() 返回一個Method對象
*
* */
public class ReflectMethod {
public static void main(String[] args) throws Exception {
Class c = Class.forName("ReflectDemo.Person");
Object obj = c.newInstance();
// // 1. 獲取class對象中的所有公共成員方法 getMethods(),包括繼承的
// Method [] methods = c.getMethods();
// for (Method method : methods) {
// System.out.println(method);
// }
// 2. 獲取空參成員方法並運行getMethod(傳遞字符串型的方法名)
// Method m1 = c.getMethod("eat");
// System.out.println(m1);
// // 調用Object invoke(Object obj,Object ...o),運行method
// m1.invoke(obj);
//
// 3. 獲取有參成員方法並運行getMethod(String name,)
Method method = c.getMethod("sleep", String.class,int.class,double.class);
// 調用Method類的方法invoke運行sleep
method.invoke(obj, "休眠",5,20);
}
}
3 反射練習
3.1 反射泛型擦除:其實程序編譯後產生的.class文件中是沒有泛型約束的,這種現象我們稱爲泛型的擦除
我們可以通過反射技術,來完成向有泛型約束的集合中,添加任意類型的元素,但是這樣的集合沒有實際的使用價值。
* a. 使用情況
例如:在泛型爲String的集合裏,添加Integer的數據
ArrayList<String> list = new ArrayList<String>();
list.add(100);
* b. 能用泛型擦除的理論
僞泛型:在編譯後的.class文件裏面是沒有泛型的。類型爲Object。
用反射的方法繞過編譯,得到Class文件對象,直接調用add方法。
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 定義集合類,泛型String,要求想集合中添加Integer類型
* 反射方式,獲取出幾個ArrayList類的class文件對象
* 通過class文件對象,調用add()方法
*
* */
public class ReflectTest {
public static void main(String[] args) throws Exception {
ArrayList<String> array = new ArrayList<String>();
array.add("a");
// 反射方式,獲取出ArrayList類的class文件對象
Class c = array.getClass();
// 獲取ArrayList.class文件中的方法add
Method method = c.getMethod("add", Object.class);
// 使用invoke運行ArrayList方法add
method.invoke(array, 150);
System.out.println(array);
}
}
3.2 反射配置文件
- 通過反射配置文件config.properties,運行配置文件中指定類的對應方法
- 讀取配置文件,調用指定類中的對應方法
配置文件:
className=ReflectDemo.Person
methodName=eat
package ReflectDemo;
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;
/*
* 調用Person方法,調用Student方法,調用Worker方法
* 通過配置文件實現此功能
* 運行的類名和方法名字,以鍵值對的形式,寫在文本中
* 運行哪個類,讀取對應的配置文件即可
* 實現步驟:
* 1. 準備配置文件,鍵值對(類名和方法名)
* 2. IO流讀取配置文件 Reader
* 3. 文件中的鍵值對存儲在集合Properties
* 4. 反射獲取指定類的class文件對象,以及方法
* 5. 運行方法
* */
public class ReflectProperties {
public static void main(String[] args) throws Exception {
// IO流讀取配置文件
FileReader r = new FileReader("config.properties");
// 創建集合對象
Properties pro = new Properties();
// 調用集合方法load,傳遞流對象
pro.load(r);
r.close();
// 通過鍵獲取值
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
// 反射獲取指定類的class文件對象,指定的方法名
Class c = Class.forName(className);
Method m = c.getMethod(methodName);
// 運行方法
m.invoke(c.newInstance());
}
}