Java中的反射技術--小白新手向

曾木欠發表於2020-11-19

反射的原理:將一個類中的各部分封裝成其他物件

反射的好處:
1.可以在程式執行中,操作這些物件
2.可以解耦,提高程式的可擴充套件性

下面用一副我畫的圖來簡單解釋一下Java程式在計算機中執行經歷的階段,以及各階段我們用反射技術是如何建立物件的
反射的原理

上圖我已經寫出獲取Class物件的三個方式:
1.class.forName(“全類名(包名.類名)”):將位元組碼檔案載入進記憶體,返回class物件
2.類名.class;通過類名的屬性class獲取
3.物件.getClass():getClass()方法在Object類中定義

先做個實驗來熟悉一下建立Class物件的方法

package reflect;

import person_reflect.Person;

public class reflect_01 {
public static void main(String[] args) throws ClassNotFoundException {
	//1.Class.forName(全類名)
	Class c1 = Class.forName("person_reflect.Person");
	System.out.println(c1);
	
	//2.類名.class	
	Class c2 = Person.class;
	System.out.println(c2);
	
	//3.物件.getClass();
	Person p = new Person();
	Class c3 =  p.getClass();
	System.out.println(c3);
	
	//驗證一下這三個物件一不一樣
	System.out.println(c1==c2);
	System.out.println(c2==c3);
	System.out.println(c1==c3);
	//結論: 同一個位元組碼檔案(*.class)在一次程式的執行過程中,只會被載入一次,不論通過哪種方法獲取到的物件都是同一個
}
}

從上述程式碼中我們可以看出,同一個位元組碼檔案在一次程式執行過程中,只會被載入一次,不論通過哪一種方式獲取的Class物件都是同一個

下面我們來正式學習一下反射的實際用法
首先建立一個Person類

package person_reflect;

public class Person {

@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + "]";
	}
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;
	}
private String name;
private int age;
public int a;
private int b;
int c;
private int d;
public Person(String name, int age) {
	super();
	this.name = name;
	this.age = age;
}
public Person() {}
public void eat () {
	System.out.println("eat");
}
public void eat(String food) {
	System.out.println("eat"+food);
}
}

一個類中首先我們能想到什麼,肯定是成員變數,那麼我們就說一下利用反射技術獲取類成員變數的方法

 * Field[] getFields():獲取所有public修飾的成員變數
 * Field getfield(String name):獲取指定名稱的成員變數
 * 
 * Field[] getDeclaredField()
 * Field getDeclaredField(String name)

首先我們要獲取Person類的Class類物件

Class pc = Person.class;

然後我們就可以用到上面的四種方法了
先用Field[] getFields():舉例

java.lang.reflect.Field[] fields = pc.getFields();
for(java.lang.reflect.Field field :fields){
		System.out.println(field);
	}

利用這個方法,我們可以獲取所有用public修飾的成員變數
那麼如果我們想要獲取類中特定的變數怎麼辦呢?

	java.lang.reflect.Field field = pc.getField("a");
	System.out.println(field);

這時候我們就可以用到getField(String name);方法了,注意這個方法是要傳進去一個變數名的字串變數的。
那麼我們獲取變數的目的是什麼呢,肯定是獲得值或者給其賦值啦
和類中的get/set一樣,Class類物件獲得目標類的變數值和賦值時也有get/set方法

java.lang.reflect.Field a = pc.getField("a");
	Person p = new Person();
	Object value =   a.get(p);//獲取Person類中public修飾的成員變數a的值
	System.out.println(value);
	//給Person中public修飾的成員變數a賦值
	a.set(p,1);
	System.out.println(p);

上面這都是獲取public修飾符修飾的變數,如果我想要呼叫private、protect、default修飾符修飾的變數怎麼辦?
那就要用到 * Field[] getDeclaredField()

  • Field getDeclaredField(String name)方法了

    /* Field[] getDeclaredField():獲取類中全部的成員變數,不考慮修飾符
    Field getDeclaredField(String name):獲取類中指定名字的成員變數
    */

//獲取全部變數
java.lang.reflect.Field[] fields_1 = pc.getDeclaredFields();
	for(java.lang.reflect.Field field_1 : fields_1) {
		System.out.println(field_1);
	}
	//獲取d變數併為其賦值 2
	java.lang.reflect.Field field_1 = pc.getDeclaredField("d");         	
	Object value2 = field_1.get(p);
	field_1.set(p, 2);
	
	System.out.println(p);

如果執行了的小夥伴會發現,這段程式報錯了,為什麼呢?

/*
//錯誤提示
	 * Exception in thread "main" java.lang.IllegalAccessException: class reflect.reflect_02 cannot access a member of class person_reflect.Person with modifiers "private"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:693)
	at java.base/java.lang.reflect.Field.checkAccess(Field.java:1096)
	at java.base/java.lang.reflect.Field.get(Field.java:417)
	at reflect.reflect_02.main(reflect_02.java:53)
	 */	 

因為上面Person類的程式碼中我們定義了d是由private修飾符修飾的,現在我們知道,雖然getDeclaredField(String name)方法雖然可以獲取所有修飾符修飾的變數,但是獲取它的值和對其賦值可是不被允許的,那麼怎麼辦呢?
這時我們就要加上一行程式碼了

	java.lang.reflect.Field[] fields_1 = pc.getDeclaredFields();
	for(java.lang.reflect.Field field_1 : fields_1) {
		System.out.println(field_1);
	}
	java.lang.reflect.Field field_1 = pc.getDeclaredField("d");
	field_1.setAccessible(true);          	//===================所以我們要加上一行程式碼 忽略訪問許可權修飾符的安全檢查 **暴力反射**===========================
	Object value2 = field_1.get(p);

	field_1.set(p, 2);
	
	System.out.println(p);

細心的小夥伴都發現了 上下兩塊程式碼有一行不一樣,對了 就是這裡

	field_1.setAccessible(true);  

這行程式碼是暴力反射 忽略訪問許可權
解釋:private修飾的變數get或者set值是不能直接獲取的 所以我們要加上一行
獲取類的成員變數的物件名.setAccessible(true):暴力反射 忽略訪問許可權

到這獲取類中成員變數的方法就基本結束了,那麼接下來是什麼呢?對了,就是類的構造方法

同獲取類的成員變數一樣,獲取類的構造方法我們同樣有四個方法

***/*
 * 獲取類的構造方法
 * 
 * Constructor<?>[] getConstructors()
 * Constructor<T> getConstructors(類<?>... parameterTypes)
 * 
 * Constructor<?>[] getDeclaredConstructors()
 * Constructor<T> getDeclaredConstructors(類<?>... parameterTypes)
 */***

相信有的小夥伴已經發現了,這不是和獲取成員變數的方法一樣嗎?還真是,用法基本是一樣的
同樣我們先獲得Class類物件pc

	Class pc = Person.class;

因為和獲取成員變數的方法基本一樣嘛,所以這裡我只舉一個栗子,因為後面我們還有更重要的東西要說:

// Constructor<T> getConstructors(類<?>... parameterTypes)
Constructor constructor = pc.getConstructor(String.class,int.class);
	//返回一個構造器
	System.out.println(constructor);

這個方法就是獲得Person類中有引數且引數型別為String和int的構造方法

返回的這個constructor就是一個構造器 ,那麼重點來了,這個構造器能幹什麼?既然叫構造器了那麼肯定能建立點東西吧,欸,它就是用來建立物件的。
T newInstance(Object… initargs)
下面我們舉個例子,來解釋一下用這個構造器怎麼才能建立個物件出來,因為是一個物件,所以我們要用Object型別的person變數來接收這個值

//用構造器來建立物件
	Object person = constructor.newInstance("張三",11);
	System.out.println(person);
	Constructor constructor1 = pc.getConstructor();
	Object person1 = constructor1.newInstance();
	System.out.println(person1);//用一個空參的構造方法建立物件
	pc.newInstance(); // 可以直接建立無參新物件

這樣我們就用這個構造器建立了一個物件,並且我們給這個物件傳了兩個引數,下面來看看執行結果

//Person [name=張三, age=11, a=0, b=0, c=0, d=0]

這樣我們就用Class類的構造器constructor來反射建立了一個Person物件。我們也可以選擇更簡單的方法,直接用Class類物件pc反射建立一個Class類指向的類的無參物件,這裡也就是Person

pc.newInstance();

只要這一行程式碼就建立好了,我們來看一下它的無參結果

//Person [name=null, age=0, a=0, b=0, c=0, d=0]

現在構造方法和變數的獲取我們都說過了,那麼一個類中還有什麼呢?欸,對了,就是成員方法,那麼成員方法怎麼獲取呢,沒錯,還是那四個方法。

**/*
 * 獲取類的成員方法
 * Method[] getMethods()
 * Method getMethod(String name,類<?>... parameterTypes)
 * 
 * Method[] getDeclaredMethods()
 * Method getDeclaredMethod(String name,類<?>... parameterTypes)
 */**

因為都一樣,我只舉一個例子:

Class pc = Person.class;
	java.lang.reflect.Method eat_method =  pc.getMethod("eat");
	Person p = new Person();
	//因為是空參 只新增一個物件名即可
	eat_method.invoke(p);
	
	java.lang.reflect.Method eat1_method = pc.getMethod("eat", String.class);
	eat1_method.invoke(p,"shit");

	
	//獲取所有public修飾的方法  還有很多隱藏方法
	java.lang.reflect.Method[] methods = pc.getMethods();
	for(java.lang.reflect.Method method1s : methods ) {
		System.out.println(method1s);
		String name = method1s.getName();
		System.out.println(name);//獲取方法名

這裡我們就用到了多型,兩個eat方法但是引數不同,一個是無參,一個要傳進一個String引數,上面提到過,獲取帶引數的成員方法或者構造方法要怎麼樣了,沒錯就是要用**資料型別.class;**這裡我們是String.class

有細心的小夥伴應該發現了,成員方法是可以只獲取名字的 ,沒錯就是這行程式碼

		String name = method1s.getName();
		System.out.println(name);//獲取方法名

同樣,被反射的類也是可以被獲取到類名的

	String name = pc.getName();
	System.out.println(name);

執行後會發現,獲取到的類名是
person_reflect.Person

這就回到了我們一開始建立的類,是在person_reflect包下建立的,所以可以知道Class類物件獲取到的被反射的類的名字是它的包加上它的類名。

相關文章