面試官再問你什麼是反射,就把這篇文章發給他!

1 什麼是反射?

反射是一種可以間接操作目標對象的機制。當使用反射時,JVM 在運行的時候才動態加載類,對於任意類,知道其屬性和方法,並不需要提前在編譯期知道運行的對象是誰,允許運行時的 Java 程序獲取類的信息並對其進行操作。

對象的類型在編譯期就可以確定,但程序運行時可能需要動態加載一些類(之前沒有用到,故沒有加載進 jvm),使用反射可以在運行期動態生成對象實例並對其進行操作。

2 反射的原理

在獲取到 Class 對象之後,反向獲取和操作對象的各種信息。

3 反射的使用

我們先建一個類

public class People {

	private int age;
	
	private String name;
	
	private People() {
		age = 18;
		name = "Tony";
	}
	
	public People(int age,String name) {
		this.age = age;
		this.name = name;
	}
	
	private void print() {
		System.out.println(this.toString());
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
}

獲得 Class 對象

獲取 Class 對象有三種方法:

  1. 調用對象的 getClass 方法
  2. 任何數據類型都擁有的 class 屬性
  3. 通過 Class 類的靜態方法 forName(String className) 進行調用,該方法最常用

在運行期間,一個類只能有一個 Class 對象產生

獲取類的構造函數

通過 getDeclaredConstructors 方法我們可以得到類的所有構造方法。

public class Test {

	public static void main(String[] args) {
		Class c = People.class;
		//得到類的所有構造方法
		Constructor[] constructors = c.getDeclaredConstructors();
		for(int i = 0; i < constructors.length; i++) {
			//獲得構造方法的類型
			System.out.println("構造方法的類型:" + Modifier.toString(constructors[i].getModifiers()));
			//獲得構造方法的所有參數
			Class[] parametertypes = constructors[i].getParameterTypes();
			for (int j = 0; j < parametertypes.length; j++) {
	             System.out.print(parametertypes[j].getName() + " ");
			}
			System.out.println("");
		}
	}
}

返回結果如下:

在這裏插入圖片描述
通過該方法,我們可以獲取類中所有構造方法和構造方法中的參數。

獲取類中特定的構造方法

在 getDeclaredConstructor 方法中,我們未傳入參數,表示希望得到類的特定構造方法。同時在代碼中要進行異常捕獲,因爲可能不存在對應的構造方法。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//得到類的特定構造方法,無參構造方法不傳參數
			constructor = c.getDeclaredConstructor();
			//獲得構造方法的類型
			System.out.println("構造方法的類型:" + Modifier.toString(constructor.getModifiers()));
			//獲得構造方法的所有參數
			System.out.println("構造方法的參數:");
			Class[] parametertypes = constructor.getParameterTypes();
			for (int j = 0; j < parametertypes.length; j++) {
		            System.out.print(parametertypes[j].getName() + " ");
			}
		} catch (Exception e) {}
	}
}

結果如下:
在這裏插入圖片描述
如果我們想得到這個構造函數

	public People(int age,String name) {
		this.age = age;
		this.name = name;
	}

在 getDeclaredConstructor 方法中,我們可以傳入一個 Class 數組,裏面包含 int 和 java.lang.String 的 Class 對象。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Class[] p = {int.class,String.class}; 
		Constructor constructor;
		try {
			//得到類的特定構造方法,這次傳入int和java.lang.String兩個參數
			constructor = c.getDeclaredConstructor(p);
			//獲得構造方法的類型
			System.out.println("構造方法的類型:" + Modifier.toString(constructor.getModifiers()));
			//獲得構造方法的所有參數
			System.out.println("構造方法的參數:");
			Class[] parametertypes = constructor.getParameterTypes();
			for (int j = 0; j < parametertypes.length; j++) {
		            System.out.print(parametertypes[j].getName() + " ");
			}
		} catch (Exception e) {}
	}
}

結果如下:
在這裏插入圖片描述

調用構造方法

在上面。我們已經學習瞭如何獲取類中特定的構造方法,在這裏,我們不僅要獲取,還要對類的構造方法進行調用。

我們先修改類的兩個構造函數,分別加上一打印語句,代碼如下:

	private People() {
		age = 18;
		name = "Tony";
		System.out.println("private People()調用成功");
	}
	
	public People(int age,String name) {
		this.age = age;
		this.name = name;
		System.out.println("public People(int age,String name)調用成功");
	}

我們先使用這個 public 訪問類型的構造函數

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Class[] p = {int.class,String.class};
		Constructor constructor;
		try {
			constructor = c.getDeclaredConstructor(p);
			//創建實例
			constructor.newInstance(10,"HaWei");
		} catch (Exception e) {}
	}
}

結果如下
在這裏插入圖片描述
那麼我們如何通過反射調用類的 private 訪問類型的構造函數呢?其實大體與上面一樣,只是我們需要設置constructors.setAccessible(true);罷了。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//獲取類的無參構造函數
			constructor = c.getDeclaredConstructor();
			constructor.setAccessible(true);
			//創建實例
			constructor.newInstance();
		} catch (Exception e) {}
	}
}

結果如下
在這裏插入圖片描述

調用類的私有方法

我們嘗試調用一下類的這個私有方法

	private void print() {
		System.out.println(this.toString());
	}

關於調用方法,我們可以通過 getDeclaredMethod 來獲取該方法,然後通過調用 invoke 執行。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//獲取類的無參構造函數
			constructor = c.getDeclaredConstructor();
			constructor.setAccessible(true);
			//創建實例
			People obj = (People) constructor.newInstance();
			
			//獲取需要調用的方法,需要兩個參數,第一個參數是方法名,第二個參數是參數類型(本例不需要傳入參數)
			Method method = c.getDeclaredMethod("print");
			method.setAccessible(true);
			//調用方法,需要兩個參數,第一個參數是類的實例,第二個參數是方法參數
			method.invoke(obj);
			
		} catch (Exception e) {}
	}
}

結果如下:
在這裏插入圖片描述

獲取類的私有字段並修改值

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//獲取類的無參構造函數
			constructor = c.getDeclaredConstructor();
			constructor.setAccessible(true);
			//創建實例
			People obj = (People) constructor.newInstance();
			
			//修改之前的name字段值
			System.out.println("修改之前:" + obj.getName());
			
			//獲取類的name字段
			Field field = c.getDeclaredField("name");
			field.setAccessible(true);
			//修改類的私有字段
			field.set(obj,"HaWei");
			
			//修改之後的name字段值
			System.out.println("修改之後:" + obj.getName());
			
		} catch (Exception e) {}
	}
}

結果如下:
在這裏插入圖片描述

4 反射的優點

可以在運行時獲得類的內容,對於 Java 這種先編譯再運行的語言,能夠讓我們很方便的寫出靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代碼的鏈接,更加容易實現面向對象。

5 反射的缺點

  1. 會消耗一定的系統資源
  2. 反射調用方法時可以忽略權限檢查,因此可能會破壞封裝性從而導致安全問題

6 反射的用途

  1. 進行反編譯,把 .class 文件變爲 .java 文件
  2. 通過反射機制訪問 java 對象的屬性,方法
  3. 開發各種通用框架(例如 Spring),許多框架都是配置化的,爲了保證框架的通用性,可能需要根據配置文件加載不同的類或者對象,調用不同的方法,這個時候就必須使用到反射了,運行時動態加載需要的加載的對象
  4. 可以通過反射運行配置文件內容,利用反射和配置文件可以使應用程序更新時,對源碼無需進行任何修改,只需要將新類發送給客戶端,並修改配置文件即可
  5. 可以通過反射越過泛型檢查,泛型使用在編譯期,編譯過後泛型擦除,反射作用在運行期,所以是可以通過反射越過泛型檢查的

參考:Java反射技術詳解
Java基礎篇:反射機制詳解

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