1. 程式人生 > >JAVA的反射機制【通俗易懂(適合初學者)】

JAVA的反射機制【通俗易懂(適合初學者)】

反射是框架設計的靈魂

(使用的前提條件:必須先得到代表的位元組碼的Class,Class類用於表示.class檔案(位元組碼))

一、反射的概述

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
要想解剖一個類,必須先要獲取到該類的位元組碼檔案物件。而解剖使用的就是Class類中的方法.所以先要獲取到每一個位元組碼檔案對應的Class型別的物件.


以上的總結就是什麼是反射
反射就是把java類中的各種成分對映成一個個的Java物件 例如:一個類有:成員變數、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行解剖,把個個組成部分對映成一個個物件。      (其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述) 如圖是類的正常載入過程:反射的原理在與class物件。

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




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

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


Class 類的例項表示正在執行的 Java 應用程式中的類和介面。也就是jvm中有N多的例項每個類都有該Class物件。(包括基本資料型別) Class 沒有公共構造方法。Class 物件是在載入類時由 Java 虛擬機器以及通過呼叫類載入器中的defineClass 方法自動構造的。也就是這不需要我們自己去處理建立,JVM已經幫我們建立好了。


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

先寫一個Student類。

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

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


其中1.1是因為Object類中的getClass方法、因為所有類都繼承Object類。從而呼叫Object類來獲取
<span style="font-size:18px;">
package cn.yt.pojo;
//網蟲套餐類
public class NetPackage {
	private String name;
	private int talkTime;
	private int flow;
	private double price;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getTalkTime() {
		return talkTime;
	}
	public void setTalkTime(int talkTime) {
		this.talkTime = talkTime;
	}
	public int getFlow() {
		return flow;
	}
	public void setFlow(int flow) {
		this.flow = flow;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public NetPackage() {
		super();
	}
	public NetPackage(String name, int talkTime, int flow, double price) {
		super();
		this.name = name;
		this.talkTime = talkTime;
		this.flow = flow;
		this.price = price;
	}
	public NetPackage(String name) {
		super();
		this.name = name;
	}
	public NetPackage(String name, int talkTime, int flow) {
		super();
		this.name = name;
		this.talkTime = talkTime;
		this.flow = flow;
	}
	
}</span> 

獲取Class物件的三種方式的程式碼案例演示:

<span style="font-size:18px;">
package cn.yt.pojo;
@Test
	/**=====一:=====
	 * 
	 * 使用反射,首先要
	 * 1.獲取到Class型別物件
	 * 2.演示獲取Class型別物件的幾種方式
	 */
	public void test1() throws Exception{
		//1.獲取網蟲套餐類的Class型別物件方式之一:知道類名
		//任何資料型別(包括靜態屬性)都有一個"靜態"的class屬性
		Class cls1 = NetPackage.class;
		System.out.println(cls1);
		
		//2.獲取網蟲套餐類的Class型別物件方式之二:知道物件名
		NetPackage pkg = new NetPackage();//這一new 產生一個NetPackage物件,一個Class對
		Class cls2 = pkg.getClass();//獲取Class物件
		System.out.println(cls2);
		
		//3.獲取網蟲套餐類的Class型別物件方式之三:知道類的完全限定名
		Class cls3 = Class.forName("cn.yt.pojo.NetPackage");
		System.out.println(cls3);
	}
}</span>

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

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

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

@Test
	/**=====三:=====
	 * 網蟲套餐類
	 * 執行時動態獲取類的建構函式,並且呼叫指定的建構函式建立物件
	 */
	public void test3() throws Exception{
		//獲取型別物件
		Class cls1 = NetPackage.class;
		//獲取類定義的所有公共的構造方法
		Constructor[] cs = cls1.getConstructors();
		//遍歷陣列cs
		//輸出建構函式的名稱和引數型別
		for(Constructor c:cs){
			String name = c.getName();
			System.out.println("=====建構函式名稱:"+name+"=====");
			Class[] parameterTypes = c.getParameterTypes();
			for (int i = 0; i < parameterTypes.length; i++) {
				System.out.println("\t"+(i+1)+"."+parameterTypes[i].getName());
			}
		}

student類

package cn.yt.pojo;
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 cn.yt.pojo;

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('男');
	}
	
}

Console輸出:

**********************所有公有構造方法*********************************
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)
姓名:男
呼叫方法: 1.獲取構造方法:   1).批量的方法:
public Constructor[] getConstructors():所有"公有的"構造方法
            public Constructor[] getDeclaredConstructors():獲取所有的構造方法(包括私有、受保護、預設、公有)
     
  2).獲取單個的方法,並呼叫:
public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"可以是私有的,或受保護、預設、公有;

  呼叫構造方法:
Constructor-->newInstance(Object... initargs)
@Test
	/**=====二:=====
	 * 
	 * 執行時動態建立物件
	 */
	public void test2() throws Exception{
		//獲取型別物件
		Class cls1 = NetPackage.class;
		//使用型別物件的新例項方法建立物件--呼叫預設建構函式例項化
		Object obj = cls1.newInstance();
		//==>cls1.getConstructor().newInstance();
		//相當於==>NetPackage pkg = new NetPackage();
		 /**
		  * T	newInstance()
		  * 建立此 Class 物件所表示的類的一個新例項。 
		  */
	}
newInstance是 Constructor類的方法(管理建構函式的類) api的解釋為: newInstance(Object... initargs)
           使用此 Constructor 物件表示的構造方法來建立該構造方法的宣告類的新例項,並用指定的初始化引數初始化該例項。
它的返回值是T型別,所以newInstance是建立了一個構造方法的宣告類的新例項物件。併為之呼叫

3、獲取成員變數並呼叫

student類:


<span style="font-size:14px;">package fanshe.field;

public class Student {
	public Student(){
		
	}
	//**********欄位*************//
	public String name;
	protected int age;
	char sex;
	private String phoneNum;
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", sex=" + sex
				+ ", phoneNum=" + phoneNum + "]";
	}
	
	
}</span>

測試類:

<span style="font-size:14px;">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);
			
		}
	}</span><span style="font-size:18px;">
</span>
Console輸出:
************獲取所有公有的欄位********************
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=
由此可見 呼叫欄位時:需要傳遞兩個引數: Object obj = stuClass.getConstructor().newInstance();//產生Student物件--》Student stu = new Student();
//為欄位設定值
f.set(obj, "劉德華");//為Student物件中的name屬性賦值--》stu.name = "劉德華"
第一個引數:要傳入設定的物件,第二個引數:要傳入實參

4、獲取成員方法並呼叫

	@Test
	/**====四:=====
	 * 網蟲套餐類
	 * 執行時動態獲取類的公共方法
	 */
	public void test4(){
		//獲取型別物件
		Class cls1 = NetPackage.class;
		//獲取類定義的所有公共方法
		Method[] methods = cls1.getDeclaredMethods();
		//遍歷方法陣列
		for (Method m:methods) {
			System.out.println("------------------");
			System.out.println("方法名"+m.getName());
			System.out.println("返回型別"+m.getReturnType());
			Class<?>[] pTypes = m.getParameterTypes();
			System.out.println("引數型別");
			for(Class<?> p:pTypes){
				System.out.println("\t"+p.getName());
			}
			System.out.println("----------------------------");
		}
	}
student類:

<span style="font-size:14px;">package fanshe.method;

public class Student {
	//**************成員方法***************//
	public void show1(String s){
		System.out.println("呼叫了:公有的,String引數的show1(): s = " + s);
	}
	protected void show2(){
		System.out.println("呼叫了:受保護的,無參的show2()");
	}
	void show3(){
		System.out.println("呼叫了:預設的,無參的show3()");
	}
	private String show4(int age){
		System.out.println("呼叫了,私有的,並且有返回值的,int引數的show4(): age = " + age);
		return "abcd";
	}
}
</span>

測試類:

<span style="font-size:14px;">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);
		
		
	}
}
</span>

Console輸出:

***************獲取所有的”公有“方法*******************
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
由此可見: m = stuClass.getDeclaredMethod("show4", int.class);//呼叫制定方法(所有包括私有的),需要傳入兩個引數,第一個是呼叫的方法名稱,第二個是方法的形參型別,切記是型別。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要兩個引數,一個是要呼叫的物件(獲取有反射),一個是實參
System.out.println("返回值:" + result);
//