1. 程式人生 > >java中反射的應用

java中反射的應用

反射就是把java類中的各種成分對映成為相應的java類,這句話是在某視訊中看到的,感覺頗有道理。

說的再明白一點,就是通過class檔案去使用該檔案中的成員變數,構造方法和成員方法。

反射技術可以動態的獲取類以及類中的成員,並且可以呼叫該類的成員,提高了程式的擴充套件性。但是反射技術有一個缺點就是降低了程式執行的效率。

 

一、使用反射技術去解決一些問題,首先要做的即使如何獲取該類的位元組碼檔案物件。有以下三種方式:

1、通過物件獲取,呼叫物件的getClass()方法,返回位元組碼檔案物件。

Student student = new Student();
Class class1 = student.getClass();

2、通過類名獲取,類名.class,返回位元組碼檔案物件。

Class class2 = Student.class;

3、通過類名的全路徑方式獲取,Class.forName("類的全路徑")。

Class class3 = Class.forName("reflect_test.Student");

必須要知道,在記憶體中,我們所要的類的位元組碼檔案只有一份,所以不管我們通過什麼樣的方式獲取,也不管我們獲取的位元組碼檔案物件的物件名是否一樣,他們都是相等的。

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		//方式一,通過物件獲取
		Student student = new Student();
		Class class1 = student.getClass();
		
		//方式二,通過類獲取
		Class class2 = Student.class;
		
		//方式三,通過全路徑的方式獲取
		Class class3 = Class.forName("reflect_test.Student");
		
		System.out.println(class1 == class2);
		System.out.println(class2 == class3);
	}
}

執行結果:

在這必須說明的是,在專案中我們通常使用的是第三種方式,即通過全路徑的方式獲取位元組碼檔案物件,這樣我們就可以通過傳遞一個字串的方式將類的全路徑傳遞過來,提高了程式的擴充套件性和可維護性。

 

二、有了位元組碼檔案物件,我們就可以獲取該類所有的構造方法:

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		//全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");
		
		//獲取該類的所有構造方法
		Constructor[] constructors = classX.getConstructors();
		
		for (Constructor constructor : constructors) {
			System.out.println(constructor);
		}
	}
}

執行結果:

public reflect_test.Student()
public reflect_test.Student(java.lang.String,int)

當然,我們也可以獲取單獨的構造:

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		//全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");
		
		//獲取該類的無參構造方法
		Constructor constructor = classX.getConstructor();
		
		System.out.println(constructor);
	}
}

注意和上邊方法的區別。

帶參構造也可以單獨獲取,方法是getConstructor(Class<?>... parameterTypes),返回一個 Constructor 物件,它反映此 Class 物件所表示的類的指定公共構造方法。呼叫這個方法的前提是必須知道所要獲取的構造的引數列表是什麼。

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		// 全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");

		// 獲取該類的無參構造方法
		Constructor constructor = classX.getConstructor(String.class, int.class);

		System.out.println(constructor);
	}
}

 

三、獲取構造的目的就是利用構造來構造類的物件了。

通過構造獲取例項的方法:newInstance(Object... initargs),使用此 Constructor 物件表示的構造方法來建立該構造方法的宣告類的新例項,並用指定的初始化引數初始化該例項。

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		// 全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");

		// 獲取該類的帶參構造方法
		Constructor constructor = classX.getConstructor(String.class, int.class);

		Student student = (Student) constructor.newInstance("張無忌", 25);
		
		System.out.println(student);
	}
}

注意:因為newInstance()方法返回值的型別是Object,所以要進行向下轉型。

這裡我們重寫了toString()方法,將這個獲取的例項打印出來:

Student [name=張無忌, age=25]

有了類的例項,我們就可以使用該例項獲取該類的成員變數以及使用該類的成員方法了。

 

四、我們也可以通過反射獲取該類的成員變數欄位。

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		// 全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");

		// 獲取該類的帶參構造方法
		Constructor constructor = classX.getConstructor(String.class, int.class);

		Student student = (Student) constructor.newInstance("張無忌", 25);

		Field field1 = classX.getField("name");

		String name = (String) field1.get(student);

		System.out.println(name);
	}
}

注意:通過getField()獲取的欄位物件不是成員變數,而是一個Field型別的物件,該物件中儲存了name的實際變數值。

對於該類中私有的Field欄位,如果我們想要訪問,就必須進行暴力反射setAccessible(true),同時獲取欄位的方法要使用getDeclaredField()獲取所有已宣告的欄位,包括private。

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		// 全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");

		// 獲取該類的帶參構造方法
		Constructor constructor = classX.getConstructor(String.class, int.class);

		Student student = (Student) constructor.newInstance("張無忌", 25);

		Field field1 = classX.getField("name");
		Field field2 = classX.getDeclaredField("age");
		
		field2.setAccessible(true);
		
		String name = (String) field1.get(student);
		int age = (int) field2.get(student);

		System.out.println(name+": "+age);
	}
}

執行結果:

 

五、通過反射獲取類中的成員方法,舉一反三,依次類推。

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		// 全路徑獲取位元組碼檔案物件
		Class classX = Class.forName("reflect_test.Student");

		// 獲取該類的帶參構造方法
		Constructor constructor = classX.getConstructor(String.class, int.class);

		// 建立例項物件
		Student student = (Student) constructor.newInstance("張無忌", 25);

		// 獲取成員變數欄位
		Field field1 = classX.getField("name");
		Field field2 = classX.getDeclaredField("age");

		// 獲取成員方法
		Method method1 = classX.getMethod("show", String.class);
		Method method2 = classX.getDeclaredMethod("study");
		Method method3 = classX.getMethod("love");
		
		// 暴力反射
		field2.setAccessible(true);
		method2.setAccessible(true);

		method1.invoke(student, "倚天屠龍記");
		method2.invoke(student);
		method3.invoke(null);

		// 獲取欄位值
		String name = (String) field1.get(student);
		int age = (int) field2.get(student);

		System.out.println("我叫" + name + ", 年齡" + age + "歲。");
	}
}

程式碼執行結果:

我是倚天屠龍記中的主角!
我練乾坤大挪移!
我愛趙敏!
我叫張無忌, 年齡25歲。

需要注意的是,欄位的反射在獲取欄位值的時候需要傳遞欄位值的物件,方法同樣如此,用來表明是哪個物件的方法或者欄位。只有一種情況例外,即方法或者欄位值被static修飾。這樣就不需要通過物件,可以直接訪問。

 

六、Student類程式碼

public class Student {
	public String name;
	private int age;

	public Student() {
		super();
	}

	public Student(String name, int age) {
		super();
		this.name = name;
		this.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 show(String str) {
		System.out.println("我是" + str + "中的主角!");
	}

	private void study() {
		System.out.println("我練乾坤大挪移!");
	}

	public static void love() {
		System.out.println("我愛趙敏!");
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
}