前言
前不久學習了反射機制,來總結下。
在此之前,回顧下java程式的編譯執行過程,分為三個階段:原始碼(.java檔案)進過編譯生成位元組碼檔案(.class檔案),然後jvm載入位元組碼檔案執行程式(runtime)。
前兩個步驟(編譯階段)是在硬碟上完成的,後一個步驟(執行階段)是在記憶體中完成的,而中間這個銜接就是:jvm通過類載入器—-ClassLoader把硬碟中的class檔案載入到記憶體中生成一個Class類的物件,這樣就可以使用這個類中的成員變數和方法。一個類預設只會被載入一次,所以這個類對應的Class物件有且僅有一個。
什麼是java反射機制?
1983年Smith首次提出反射這個概念,主要指程式可以訪問、檢測和修改他本身狀態或行為的一種能力。
java反射機制是在執行狀態中中對類進行解剖並操作類中的構造方法,成員方法,成員屬性(主要用於框架中),這種動態獲取資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
Class物件和反射機制的聯絡。
瞭解了反射機制的概念,那麼可見要想利用java反射機制做一些事,那麼就要利用Class物件,所以說Class物件是反射的前提。
那麼,怎麼獲取Class物件?
java中有三種方式獲取Class物件:
-
類名.class
-
物件名.getClass
-
Class.forName(“全限定名(包名 + 類名)”);
補充:Class物件分兩種
1.普通Class物件:基於 引用型別
2.預定義(在jvm中的)Class物件:基於 基本型別 和 void
反射機制的幾種作用:
- 在執行時判斷任意一個物件所屬的類
- 在執行時構造任意一個類的物件
- 在執行時判斷任意一個類所具有的成員變數和方法
- 在執行時呼叫任意一個物件的方法
先準備一個類:
package com.test.demo;
public class Student {
public String name;
private int age;
public Student() {
}
private Student(String name, int age) {
this.name = name;
this.age = age;
}
public void show(String msg){
System.out.println("show方法 = " + msg);
}
private void speak(String msg,int number){
System.out.println("speak方法 = " + msg +":"+ number );
}
@Override
public String toString() {
return "Student{" +
"name=`" + name + ``` +
", age=" + age +
`}`;
}
}
複製程式碼
反射的使用1:構造器(Constructor)的反射
再次之前,我們可以通過公共的空參構造new一個Student,但是無法new私有的滿參構造。
Student student = new Student();
複製程式碼
現在來反射構造構造器(反射的形式建立例項)
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
//獲取Class物件
Class<?> clazz = Student.class;
/*
根據引數型別獲取相應的構造器
引數型別是形參型別
*/
Constructor<?> constructor = clazz.getConstructor();
/*
建立例項
引數型別是實參型別(形參一一對應)
*/
Object obj = constructor.newInstance();
System.out.println("obj = " + obj);
}
複製程式碼
這樣獲取到的Student物件和new出來的空參構造器new出來的物件效果一樣的(實際業務開發並沒有意義)。
前者通過new建立出來物件的方式相比用反射建立的物件更被動,前者 是被new出來的,而用反射,是自己建立自己(物件),構造方法反客為主。
還有一種方式,就是直接通過Class物件建立構造器:
public static void main(String[] args)
throws IllegalAccessException, InstantiationException {
//獲取Class物件
Class<?> clazz = Student.class;
/*
預設呼叫空參構造建立一個例項
jdk9中已過時
*/
Object obj = clazz.newInstance();
System.out.println("obj = " + obj);
}
複製程式碼
在Student類中 ,還有一個私有的構造器,正常方式下是不能通過私有構造器建立物件的。,但是反射可以做到:
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
//獲取Class物件
Class<?> clazz = Student.class;
/*
獲取構造
因為許可權是私有,但getConstructor()只能獲取public修飾的方法
getDeclaredConstructor():獲取宣告的方法。只要宣告的就可以
*/
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println("滿參私有構造 :" + constructor);
/*
私有構造,newInstance會產生非法訪問異常:java.lang.IllegalAccessException
所以要改變許可權setAccessible() -->暴力反射
*/
constructor.setAccessible(true);
Object obj = constructor.newInstance("小明",20);
System.out.println("obj = " + obj);
}
複製程式碼
以上就是利用反射來建立一個物件(反射構造器)。
反射的使用2:方法(Method)的反射
接下來看看Student物件內兩個方法的反射
我們之前(外部)使用方法,都是都是通過物件呼叫(非私有)方法,如果是靜態方法就是類直接呼叫。
那麼,使用反射呼叫(非私有)方法,該怎麼做?
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
//獲取Class物件
Student student = new Student();
Class<? extends Student> clazz = student.getClass();
/*
getMethod():獲取Class物件裡的方法
引數一:方法名
引數二:引數列表型別
*/
Method show = clazz.getMethod("show", String.class);
/*
呼叫show方法需要物件和引數
invoke()方法:呼叫的意思
引數一:呼叫此方法的物件
引數二:呼叫此方法需要傳入的實參
*/
show.invoke(student, "hello public show");
}
複製程式碼
反射可以理解為語言語法上的倒裝句:
我們平時寫程式碼都是我(物件)去呼叫方法,這裡就是:
new Student().show(“物件呼叫方法”);
而在 show.invoke(student, “hello public show”); 中,
show方法考慮的是誰來呼叫我,然後Student物件說,我來呼叫你(student作為引數)。
擴充套件:如果公共的show方法加上static關鍵字,會影響方法呼叫嗎?
提示:靜態與物件無關.
答:加上static關鍵字,普通程式碼即使不new物件也可以呼叫,這個大家都知道,那麼,在show.invoke(student, “hello public show”); 中引數1 寫 null 也是不影響的,因為,show方法來自於 Student的Class物件。
接下來看看私有方法的反射如何實現?
ps: 反射通道的API都很有規律,可讀性很強
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
//獲取Class物件
Student student = new Student();
Class<? extends Student> clazz = student.getClass();
/*
getDeclaredMethod():獲取Class物件裡的宣告過的方法(包括)
引數一:方法名
引數二:引數列表型別
*/
Method speak = clazz.getDeclaredMethod("speak", String.class, int.class);
//私有方法,暴力反射
speak.setAccessible(true);
/*
呼叫show方法需要物件和引數
invoke()方法:呼叫的意思
引數一:呼叫此方法的物件
引數二:呼叫此方法需要傳入的實參
*/
speak.invoke(student, "hello private speak",2018);
}
複製程式碼
反射的使用3:屬性(Field)的反射
在Student實體中有一個共有屬性一個私有屬性,我們可以通過物件來設定共有屬性的值,那麼通過反射如何實現所有屬性的賦值?
先來看看共有屬性name的賦值
public static void main(String[] args)
throws ClassNotFoundException, NoSuchFieldException,
IllegalAccessException, InstantiationException {
//獲取Class物件,引數全限定名
Class<?> clazz = Class.forName("com.test.demo.Student");
/*
getField():通過屬性名獲取屬性
*/
Field name = clazz.getField("name");
//獲取物件
Object obj = clazz.newInstance();
/*
設定一個值
引數一:哪個物件的屬性值
引數二:引數
*/
name.set(obj,"張三");
System.out.println(obj);
}
複製程式碼
根據前面說的API,反射屬性不難理解。
私有屬性的反射也不難實現
public static void main(String[] args)
throws ClassNotFoundException, NoSuchFieldException,
IllegalAccessException, InstantiationException {
//獲取Class物件,引數全限定名
Class<?> clazz = Class.forName("com.test.demo.Student");
/*
getDeclaredField():通過屬性名獲取(所有許可權)屬性
*/
Field age = clazz.getDeclaredField("age");
//暴力反射
age.setAccessible(true);
//建立物件
Object obj = clazz.newInstance();
/*
設定一個值
引數一:哪個物件的屬性值
引數二:引數
*/
age.set(obj,20);
System.out.println(obj);
}
複製程式碼
總結:
使用java的反射機制,一般需要遵循三步:
- 獲得你想操作類的Class物件
- 通過第一步獲得的Class物件去取得操作類的方法或是屬性名
- 操作第二步取得的方法或是屬性
那麼反射到底有什麼用?
反射最主要還是運用在框架中,瞭解反射(不止反射)才更好的瞭解一些框架的原理。