JavaDay16 反射

今日內容介紹

  1. 類加載器
  2. 反射構造方法
  3. 反射成員變量
  4. 反射成員方法
  5. 反射配置文件運行類中的方法

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 反射配置文件

  1. 通過反射配置文件config.properties,運行配置文件中指定類的對應方法
  2. 讀取配置文件,調用指定類中的對應方法

配置文件:

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());
	}

}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章