1. 程式人生 > >java反射原理

java反射原理

一、反射機制

在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態(在執行時)獲取類的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。簡單來說,就是Java對每一個類和類中的所有成員都進行了封裝,這樣每個類都有一個與之對應的Class物件(編譯之後產生的class物件),通過這個物件可以直接訪問類中的所有成員。

1. Java 反射機制主要提供了以下功能

    1.1、在執行時判斷任意一個物件所屬的類。
    1.2、在執行時構造任意一個類的物件。
    1.3、在執行時判斷任意一個類所具有的成員變數和方法。
    1.4、在執行時呼叫任意一個物件的方法

    1.5、生成動態代理。

2. 反射機制的優點與缺點

首先要搞清楚為什麼要用反射機制?直接建立物件不就可以了嗎,這就涉及到了動態與靜態的概念。 
靜態編譯:在編譯時確定型別,繫結物件,即通過。 
動態編譯:執行時確定型別,繫結物件。動態編譯最大限度發揮了java的靈活性,體現了多型的應用,有以降低類之間的藕合性。 

反射機制的優點:可以實現動態建立物件和編譯,體現出很大的靈活性(特別是在J2EE的開發中它的靈活性就表現的十分明顯)。通過反射機制我們可以獲得類的各種內容,進行了反編譯。對於JAVA這種先編譯再執行的語言來說,反射機制可以使程式碼更加靈活,更加容易實現面向物件。

  比如,一個大型的軟體,不可能一次就把把它設計的很完美,當這個程式編譯後,釋出了,當發現需要更新某些功能時,我們不可能要使用者把以前的解除安裝,再重新安裝新的版本,假如這樣的話,這個軟體肯定是沒有多少人用的。採用靜態的話,需要把整個程式重新編譯一次才可以實現功能的更新,而採用反射機制的話,它就可以不用解除安裝,只需要在執行時才動態的建立和編譯,就可以實現該功能。 

反射機制的缺點:對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它 滿足我們的要求。這類操作總是慢於只直接執行相同的操作。

Reflection 是Java被視為動態(或準動態)語言的一個關鍵性質。這個機制允許程式在執行時透過Reflection APIs取得任何一個已知名稱的class的內部資訊,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實現之interfaces(例如Serializable),也包括fields和methods的所有資訊,並可於執行時改變fields內容或呼叫methods。

3.以上的總結就是什麼是反射

Java程式可以載入一個執行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其物件實體、或對其fields設值、或喚起其methods。這種“看透class”的能力(the ability of the program to examine itself)被稱為introspection(內省、內觀、反省)。Reflection和introspection是常被並提的兩個術語。

反射就是把java類中的各種成分對映成一個個的Java物件。

在JDK中,主要由以下類來實現Java反射機制,這些類都位於java.lang.reflect包中
1、Class類:代表一個類。
2、Field 類:代表類的成員變數(成員變數也稱為類的屬性)。
3、Method類:代表類的方法。
4、Constructor 類:代表類的構造方法。
5、Array類:提供了動態建立陣列,以及訪問陣列的元素的靜態方法

例如:一個類有:成員變數、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行解剖,把個個組成部分對映成一個個物件。

     (其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述)

如圖是類的正常載入過程:反射的原理在與class物件。

熟悉一下載入的時候:Class物件的由來是將class檔案讀入記憶體,併為之建立一個Class物件。

其中這個Class物件很特殊。我們先了解一下這個Class類

二、檢視Class類在java中的api詳解(1.7的API)

Class 類的例項表示正在執行的 Java 應用程式中的類和介面。也就是jvm中有N多的例項每個類都有該Class物件。(包括基本資料型別)

Class 沒有公共構造方法。Class 物件是在載入類時由 Java 虛擬機器以及通過呼叫類載入器中的defineClass方法自動構造的。也就是這不需要我們自己去處理建立,JVM已經幫我們建立好了。

沒有公共的構造方法,方法共有64個太多了。下面用到哪個就詳解哪個吧

三、反射的使用(這裡使用Student類做演示)

先寫一個Student類。

1、獲取Class物件的三種方式

1.1 Object ——> getClass();
1.2 任何資料型別(包括基本資料型別)都有一個“靜態”的class屬性
1.3 通過Class類的靜態方法:forName(String  className)(常用)

其中1.1是因為Object類中的getClass方法、因為所有類都繼承Object類。從而呼叫Object類來獲取

package fanshe;
/**
 * 獲取Class物件的三種方式
 * 1 Object ——> getClass();
 * 2 任何資料型別(包括基本資料型別)都有一個“靜態”的class屬性
 * 3 通過Class類的靜態方法:forName(String  className)(常用)
 *
 */
public class Fanshe {
	public static void main(String[] args) {
		//第一種方式獲取Class物件  
		Student stu1 = new Student();//這一new 產生一個Student物件,一個Class物件。
		Class stuClass = stu1.getClass();//獲取Class物件
		System.out.println(stuClass.getName());
		
		//第二種方式獲取Class物件
		Class stuClass2 = Student.class;
		System.out.println(stuClass == stuClass2);//判斷第一種方式獲取的Class物件和第二種方式獲取的是否是同一個
		
		//第三種方式獲取Class物件
		try {
			Class stuClass3 = Class.forName("fanshe.Student");//注意此字串必須是真實路徑,就是帶包名的類路徑,包名.類名
			System.out.println(stuClass3 == stuClass2);//判斷三種方式是否獲取的是同一個Class物件
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
	}
}

注意:在執行期間,一個類,只有一個Class物件產生。

三種方式常用第三種,第一種物件都有了還要反射干什麼。第二種需要匯入類的包,依賴太強,不導包就拋編譯錯誤。一般都第三種,一個字串可以傳入也可寫在配置檔案中等多種方法。

2、執行時通過反射獲取構造方法並使用:

student類:

package fanshe;
 
public class Student {
	
	//---------------構造方法-------------------
	//(預設的構造方法)
	Student(String str){
		System.out.println("(預設)的構造方法 s = " + str);
	}
	
	//無參構造方法
	public Student(){
		System.out.println("呼叫了公有、無參構造方法執行了。。。");
	}
	
	//有一個引數的構造方法
	public Student(char name){
		System.out.println("姓名:" + name);
	}
	
	//有多個引數的構造方法
	public Student(String name ,int age){
		System.out.println("姓名:"+name+"年齡:"+ age);//這的執行效率有問題,以後解決。
	}
	
	//受保護的構造方法
	protected Student(boolean n){
		System.out.println("受保護的構造方法 n = " + n);
	}
	
	//私有構造方法
	private Student(int age){
		System.out.println("私有的構造方法   年齡:"+ age);
	}
 
}

共有6個構造方法;

測試類:

package fanshe;
 
import java.lang.reflect.Constructor;
 
 
/*
 * 通過Class物件可以獲取某個類中的:構造方法、成員變數、成員方法;並訪問成員;
 * 
 * 1.獲取構造方法:
 * 		1).批量的方法:
 * 			public Constructor[] getConstructors():所有"公有的"構造方法
            public Constructor[] getDeclaredConstructors():獲取所有的構造方法(包括私有、受保護、預設、公有)
     
 * 		2).獲取單個的方法,並呼叫:
 * 			public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法:
 * 			public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"可以是私有的,或受保護、預設、公有;
 * 		
 * 			呼叫構造方法:
 * 			Constructor-->newInstance(Object... initargs)
 */
public class Constructors {
 
	public static void main(String[] args) throws Exception {
		//1.載入Class物件
		Class clazz = Class.forName("fanshe.Student");
		
		
		//2.獲取所有公有構造方法
		System.out.println("**********************所有公有構造方法*********************************");
		Constructor[] conArray = clazz.getConstructors();
		for(Constructor c : conArray){
			System.out.println(c);
		}
		
		
		System.out.println("************所有的構造方法(包括:私有、受保護、預設、公有)***************");
		conArray = clazz.getDeclaredConstructors();
		for(Constructor c : conArray){
			System.out.println(c);
		}
		
		System.out.println("*****************獲取公有、無參的構造方法*******************************");
		Constructor con = clazz.getConstructor(null);
		//1>、因為是無參的構造方法所以型別是一個null,不寫也可以:這裡需要的是一個引數的型別,切記是型別
		//2>、返回的是描述這個無參建構函式的類物件。
	
		System.out.println("con = " + con);
		//呼叫構造方法
		Object obj = con.newInstance();
	//	System.out.println("obj = " + obj);
	//	Student stu = (Student)obj;
		
		System.out.println("******************獲取私有構造方法,並呼叫*******************************");
		con = clazz.getDeclaredConstructor(char.class);
		System.out.println(con);
		//呼叫構造方法
		con.setAccessible(true);//暴力訪問(忽略掉訪問修飾符)
		obj = con.newInstance('男');
	}
	
}

後臺輸出

**********************所有公有構造方法*********************************
public fanshe.Student(java.lang.String,int)
public fanshe.Student(char)
public fanshe.Student()
************所有的構造方法(包括:私有、受保護、預設、公有)***************
private fanshe.Student(int)
protected fanshe.Student(boolean)
public fanshe.Student(java.lang.String,int)
public fanshe.Student(char)
public fanshe.Student()
fanshe.Student(java.lang.String)
*****************獲取公有、無參的構造方法*******************************
con = public fanshe.Student()
呼叫了公有、無參構造方法執行了。。。
******************獲取私有構造方法,並呼叫*******************************
public fanshe.Student(char)
姓名:男

欲生成物件實體,在Reflection 動態機制中有兩種作法:

一個針對“無自變數構造方法”,直接呼叫Class的newInstance()建立例項物件;

一個針對“帶引數構造方法”,需要呼叫Constructor 的newInstance()。首先確定該類的構造方法的Class型別物件(如果多個形參,使用Class[]陣列儲存引數型別(本例指定為一個double和一個int),然後該類的Class物件呼叫getConstructor(),獲得一個構造器物件ctor。接下來呼叫這個構造方法時,準備一個Object放置實參(多個實參,使用Object陣列傳參(本例指定3.14159和125),然後ctor呼叫newInstance()方法。

動態生成“Class object 所對應之class”的物件實體,無參構造方法。

動態生成“Class object 所對應之class”的物件實體,有參構造方法。

3、執行時獲取成員變數並呼叫

package fanshe.field;
import java.lang.reflect.Field;
/*
 * 獲取成員變數並呼叫:
 * 
 * 1.批量的
 * 		1).Field[] getFields():獲取所有的"公有欄位"
 * 		2).Field[] getDeclaredFields():獲取所有欄位,包括:私有、受保護、預設、公有;
 * 2.獲取單個的:
 * 		1).public Field getField(String fieldName):獲取某個"公有的"欄位;
 * 		2).public Field getDeclaredField(String fieldName):獲取某個欄位(可以是私有的)
 * 
 * 	 設定欄位的值:
 * 		Field --> public void set(Object obj,Object value):
 * 					引數說明:
 * 					1.obj:要設定的欄位所在的物件;
 * 					2.value:要為欄位設定的值;
 * 
 */
public class Fields {
 
		public static void main(String[] args) throws Exception {
			//1.獲取Class物件
			Class stuClass = Class.forName("fanshe.field.Student");
			//2.獲取欄位
			System.out.println("************獲取所有公有的欄位********************");
			Field[] fieldArray = stuClass.getFields();
			for(Field f : fieldArray){
				System.out.println(f);
			}
			System.out.println("************獲取所有的欄位(包括私有、受保護、預設的)********************");
			fieldArray = stuClass.getDeclaredFields();
			for(Field f : fieldArray){
				System.out.println(f);
			}
			System.out.println("*************獲取公有欄位**並呼叫***********************************");
			Field f = stuClass.getField("name");
			System.out.println(f);
			//獲取一個物件
			Object obj = stuClass.getConstructor().newInstance();//產生Student物件--》Student stu = new Student();
			//為欄位設定值
			f.set(obj, "劉德華");//為Student物件中的name屬性賦值--》stu.name = "劉德華"
			//驗證
			Student stu = (Student)obj;
			System.out.println("驗證姓名:" + stu.name);
			
			
			System.out.println("**************獲取私有欄位****並呼叫********************************");
			f = stuClass.getDeclaredField("phoneNum");
			System.out.println(f);
			f.setAccessible(true);//暴力反射,解除私有限定
			f.set(obj, "18888889999");
			System.out.println("驗證電話:" + stu);
			
		}
	}

後臺輸出

************獲取所有公有的欄位********************
public java.lang.String fanshe.field.Student.name
************獲取所有的欄位(包括私有、受保護、預設的)********************
public java.lang.String fanshe.field.Student.name
protected int fanshe.field.Student.age
char fanshe.field.Student.sex
private java.lang.String fanshe.field.Student.phoneNum
*************獲取公有欄位**並呼叫***********************************
public java.lang.String fanshe.field.Student.name
驗證姓名:劉德華
**************獲取私有欄位****並呼叫********************************
private java.lang.String fanshe.field.Student.phoneNum
驗證電話:Student [name=劉德華, age=0, sex=

4、執行時獲取成員方法並呼叫

這個動作和上述呼叫“帶引數之ctor”相當類似。獲取成員方法時,呼叫getMethod()方法,需要傳兩個引數,第一個引數:方法名,第二個引數:形參的Class型別物件(多個引數,使用Class陣列傳參)。首先準備一個Class[]做為引數型別(本例指定其中一個是String,另一個是Hashtable),然後以此為自變數呼叫getMethod(),獲得特定的Method object。接下來,要呼叫這個成員方法時,準備一個Object放置實參(多個實參,使用Object陣列傳參),然後呼叫該成員方法的Method物件的invoke()方法。為什麼獲得Method object時不需指定回返型別?

因為method overloading機制要求signature必須唯一,而回返型別並非signature的一個成份。換句話說,只要指定了method名稱和引數列,就一定指出了一個獨一無二的method。

package fanshe.method;
 
import java.lang.reflect.Method;
 
/*
 * 獲取成員方法並呼叫:
 * 
 * 1.批量的:
 * 		public Method[] getMethods():獲取所有"公有方法";(包含了父類的方法也包含Object類)
 * 		public Method[] getDeclaredMethods():獲取所有的成員方法,包括私有的(不包括繼承的)
 * 2.獲取單個的:
 * 		public Method getMethod(String name,Class<?>... parameterTypes):
 * 					引數:
 * 						name : 方法名;
 * 						Class ... : 形參的Class型別物件
 * 		public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
 * 
 * 	 呼叫方法:
 * 		Method --> public Object invoke(Object obj,Object... args):
 * 					引數說明:
 * 					obj : 要呼叫方法的物件;
 * 					args:呼叫方式時所傳遞的實參;
):
 */
public class MethodClass {
 
	public static void main(String[] args) throws Exception {
		//1.獲取Class物件
		Class stuClass = Class.forName("fanshe.method.Student");
		//2.獲取所有公有方法
		System.out.println("***************獲取所有的”公有“方法*******************");
		stuClass.getMethods();
		Method[] methodArray = stuClass.getMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
		System.out.println("***************獲取所有的方法,包括私有的*******************");
		methodArray = stuClass.getDeclaredMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
		System.out.println("***************獲取公有的show1()方法*******************");
		Method m = stuClass.getMethod("show1", String.class);
		System.out.println(m);
		//例項化一個Student物件
		Object obj = stuClass.getConstructor().newInstance();
		m.invoke(obj, "劉德華");
		
		System.out.println("***************獲取私有的show4()方法******************");
		m = stuClass.getDeclaredMethod("show4", int.class);
		System.out.println(m);
		m.setAccessible(true);//解除私有限定
		Object result = m.invoke(obj, 20);//需要兩個引數,一個是要呼叫的物件(獲取有反射),一個是實參
		System.out.println("返回值:" + result);
		
		
	}
}

後臺輸出

***************獲取所有的”公有“方法*******************
public void fanshe.method.Student.show1(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
***************獲取所有的方法,包括私有的*******************
public void fanshe.method.Student.show1(java.lang.String)
private java.lang.String fanshe.method.Student.show4(int)
protected void fanshe.method.Student.show2()
void fanshe.method.Student.show3()
***************獲取公有的show1()方法*******************
public void fanshe.method.Student.show1(java.lang.String)
呼叫了:公有的,String引數的show1(): s = 劉德華
***************獲取私有的show4()方法******************
private java.lang.String fanshe.method.Student.show4(int)
呼叫了,私有的,並且有返回值的,int引數的show4(): age = 20
返回值:abcd