天天看點

Java反射機制--基本使用方法簡介

Reflection(反射)是Java程式開發語言的特征之一,它允許運作中的 Java程式對自身進行檢查,或者說"自審",并能直接操作程式的内部屬性。例如,使用它能獲得 Java類中各成員的名稱并顯示出來。 Java的這一能力在實際應用中也許用得不是很多,但是在其它的程式設計語言中根本就不存在這一特性。例如,Pascal、C或者 C++ 中就沒有辦法在程式中獲得函數定義相關的資訊。反射機制是建構架構技術的基礎所在,靈活掌握Java反射機制,對大家以後學習架構技術有很大的幫助。

反射的應用

Java的反射機制讓它知道類的基本結構,這種“自審”的能力應用很廣泛。Eclipse大家都用過,它有一個很強大的功能:我們在編寫代碼的時候,建構一個對象,要去調用該對象的方法和屬性的時候。按下Alt+/(預設的快捷鍵)就會列出該對象可以調用的方法以及方法的參數等,更細緻的還可以得到該方法具體的描述,這個強大的功能在使用eclipse的時候表現非常突出,這種技術就是運用了Java的反射機制來實作的。

認識一下Class類

與Java關鍵字class不同的是,java.lang.Class是一個具體的類,表示了所有的Java類,是Java反射機制的基礎。

Class

沒有公共構造方法。

Class

對象是在加載類時由Java虛拟機以及通過調用類加載器中的

defineClass

方法自動構造的。擷取一個Class對象有三種方法,下面通過一個執行個體來認識一下:

class Reflection {
	public static void main(String[] args) {
		// Class cla1 = new Class();
		// Class類沒有可見的構造方法,建立Class對象方法有下面三種
		String str = "string";
		Class cla1 = str.getClass();
		Class cla2 = String.class;
		Class cla3 = null;
		try {
			cla3 = Class.forName("java.lang.String");
		} catch(ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		/*比較通過不同方法得到的Class對象是否相同*/
		System.out.println(cla1 == cla2);
		System.out.println(cla1 == cla3);
		
		/*列印Class對象所表示的實體的字元串表示形式*/
		System.out.println(cla1.getName());
		System.out.println(cla1.getPackage().toString());
		System.out.println(cla2);
		System.out.println(Class.class.getName());
	}
}
/** 列印結果
true
true
java.lang.String
java.lang.Class
*/
           

總結:

根據不同的情況使用不同的方法,三種方法應用情景是各不相同的。

存在對象的時候使用obj.getClass()

沒有對象的時候直接在類名後加class字元

上面兩種情況JVM都加載了類,如果JVM尚未加載該類,那麼就使用Class.forName方法

列印結果的第一二行說明上面三個建立Class對象的方法得到的Class對象是同一個

獲得了Class對象,可以做什麼?

既然拿到了Class對象,那麼我們可以利用其做什麼呢?

擷取該Class對象所表示的類或接口的Constructor(構造器)、Annotation(注釋)、Field(成員變量)、Method(方法)、newInstance(建立對象)等等。

我們下面就來看看如何利用這些方法吧。

1, 判斷一個對象是否屬于一個類

class Person {
}

public class IsInstance { 
   public static void main(String args[]) { 
		try {
			Class cls = Person.class;
			boolean b1 = cls.isInstance(new String("abc")); 
			System.out.println(b1); 
			boolean b2 = cls.isInstance(new Person()); 
			System.out.println(b2); 
		} 
		catch (Throwable e) { 
			System.err.println(e); 
		} 
	}
}

/** 列印結果
false
true
*/
           

在這個例子中建立了Person對象,調用Class對象的isInstance方法判斷傳入對象是否與此Class對象所表示的類或者其任一子類的執行個體,此方法等效于instanceof運算符

2, 建立類的對象

class Person {
	public static final int MAN = 1;
	public static final int WOMAN = 0;
	int sex;
	int age;
	public Person() {
		this.sex = MAN;
		this.age = 25;
	}
	public Person(int sex, int age) {
		this.sex = sex;
		this.age = age;
	}
	public int getSex() {
		return sex;
	}
	public int getAge() {
		return age;
	}
}

public class NewInstance { 
   public static void main(String args[]) { 
		try {
			Class cls = Person.class;
			Person person = (Person)cls.newInstance();
			
			boolean bool = person instanceof Person;
			System.out.println(bool);
			System.out.println(person.getSex());
			System.out.println(person.getAge());
			
		} 
		catch (Exception e) { 
			e.printStackTrace();
		} 
	}
}

/**	列印結果
true
1
25
*/
           

從列印結果上可以發現,Class對象調用newInstance方法傳回一個該Class對象所表示的類的對象的引用,相當于new Person()。

3, 擷取類的構造方法

import java.lang.reflect.*;

class ConstructorTest {
	public static void main(String[] args) {
		try {
			Class clazz = Class.forName("java.lang.String");
			Constructor cont = clazz.getConstructor(byte[].class);
			//直接列印其調用toString方法傳回的字元串
			System.out.println(cont);
		} catch(ClassNotFoundException e) {
			e.printStackTrace();
		} catch(NoSuchMethodException e) {
			e.printStackTrace();
		}
	}
}
           

通過Class類的getConstructor和getConstructors方法可以得到Class對象所表示的類的公共構造方法的對象Constructor,通過該對象可以獲得構造方法的名稱、參數類型、還可以以此構造方法聲明一個新的執行個體,并且可以指定指定的初始化參數初始化該執行個體。

4, 擷取類中的方法,并 根據方法的名稱來調用該方法

import java.lang.reflect.*;

class Person {
	public static final int MAN = 1;
	public static final int WOMAN = 0;
	int sex;
	int age;
	public Person() {
		this.sex = MAN;
		this.age = 25;
	}
	public Person(int sex, int age) {
		this.sex = sex;
		this.age = age;
	}
	public int getSex() {
		return sex;
	}
	public int getAge() {
		return age;
	}
}

class GetMethod {
	public static void main(String[] args) {
		try {
			Class clazz = Person.class;
			Constructor cont = clazz.getConstructor(int.class, int.class);
			Person person = (Person)cont.newInstance(new Object[]{Person.WOMAN, 22});
			
			Method mgetSex = clazz.getDeclaredMethod("getSex", new Class[0]);
			Method mgetAge = clazz.getDeclaredMethod("getAge", new Class[0]);
			
			int sex = (int)mgetSex.invoke(person, new Object[0]);
			int age = (int)mgetAge.invoke(person, new Object[0]);
			System.out.println("sex :" + sex);
			System.out.println("age :" + age);
		} catch(Throwable e) {
			e.printStackTrace();
		}
	}
}

/** 列印結果
sex :0
age :22
*/
           

上面的小程式先用Class對象建立了一個Person類的帶參的構造方法并利用這個構造方法傳入兩個參數執行個體化了一個Person對象,然後用getDeclaredMethod分别擷取了Person類中getSex和getAge兩個方法的Method對象,最後通過這兩個Method對象調用invoke方法

invoke(person, new Object[0]);
           

第一個參數訓示調用底層方法的對象,第二的參數表示底層方法的形參數組,這裡将數組長度設為0表示底層方法沒有參數。

通過getMethod方法還可以獲得Class對象表示的類或接口的超類或者超接口的方法。

6, 擷取、修改類的成員變量

class Student {
	private int ID;
	private int grade;
	public Student() {
		this.ID = 130000;
		this.grade = 0;
	}
	public Student(int ID, int grade) {
		this.ID = ID;
		this.grade = grade;
	}
	public int getID() {
		return ID;
	}
	public int getGrade() {
		return grade;
	}
}

class GetMethod {
	public static void main(String[] args) {
		try {
			Student Tom = new Student(231058, 225);
			Student Karry = new Student(230542, 286);
			
			Class clazz = Tom.getClass();
			
			Field field = clazz.getDeclaredField("grade");
			field.setAccessible(true);
			System.out.println("Tom'grade :" + (int)field.get(Tom));
			System.out.println("Karry'grade :" + (int)field.get(Karry));
			
			field.set(Tom, 0);
			field.set(Karry, 511);
			//偷天換日,太黑了,哈哈...
			System.out.println("Changed...\n");
			System.out.println("Tom'grade :" + (int)field.get(Tom));
			System.out.println("Karry'grade :" + (int)field.get(Karry));
			
		} catch(Throwable e) {
			e.printStackTrace();
		}
	}
}

/** 列印結果
Tom'grade :225
Karry'grade :286
Changed...

Tom'grade :0
Karry'grade :511
*/
           

哈哈,這裡我們模拟了一次“惡意”修改學分的事件,估計Tom看到自己的學分肯定要驚呆了。這裡首先是擷取一個指定字段的Field對象,這裡有點需要注意的是,由于Student類中grade屬性通路修飾符為private,是以這裡必須要使用getDeclaredField而不能是getField,因為後者隻能擷取公共字段,也就是public修飾的字段。

8, 使用數組

import java.lang.reflect.*;

class ReflectArray {
	public static void main(String[] args) {
		int array[] = new int[]{1, 4, 25, 88, 56, 57};
		boolean bool[] = new boolean[]{true, true, false, true, false};
		printArray(array);
		printArray(bool);
		
		reseveArray(array);
		reseveArray(bool);
		System.out.println("Reseved...");
		
		printArray(array);
		printArray(bool);
	}
	
	public static void printArray(Object obj) {
		Class clazz = obj.getClass();
		if(clazz.isArray()) {
			int len = Array.getLength(obj);
			for(int index = 0; index < len; index++) {
				System.out.print(Array.get(obj, index) + " ");
			}
			System.out.println();
		} else {
			System.out.println(obj);
		}
		return;
	}
	
	public static void reseveArray(Object obj) {
		Class clazz = obj.getClass();
		if(clazz.isArray()) {
			int end = Array.getLength(obj) - 1;
			/*将數組反轉*/
			for(int index = 0; index <= end; index++) {
				Object buff = Array.get(obj, index);
				Array.set(obj, index, Array.get(obj, end));
				Array.set(obj, end, buff);
				end--;
			}
		} else {
			System.out.println("Error. Not a Array.");
		}
		return;
	}
}

/** 列印結果
1 4 25 88 56 57
true true false true false
Reseved...
57 56 88 25 4 1
false true false true true
*/
           

上面簡單的例子實作了列印數組中的值和将數組的值進行反轉兩個操作,先通過Class對象的isArray方法判斷obj是不是一個數組,若是,則通過Array類擷取或者改變數組中元素的值。

這裡留下一個疑問就是能不能通過反射确定一個數組的類型呢?期待各位的見解。