深入剖析java反射原理
阿新 • • 發佈:2020-08-04
Java高階之反射
Class類
從java世界理解Class
-
問題一:類和物件的關係?
-
答曰:類是抽象的概念,它是具有相同屬性和方法的一組物件集合,它代表著事物的模板;而物件是能夠真正“感覺的到、看得見,摸得著的”具體的實體。對物件的抽象便是類,而類的例項化結果便是物件。
-
問題二:有個可能不恰當的問法:物件的抽象是類,那類的抽象用什麼表示?
- java API中有個類
java.lang.Class
,該類是用來描述類的類(比較拗口),為了幫助理解,直接上圖:
- 物件是具體的例項:比如哈士奇、泰迪、正方形、三角形;
- 類是物件的抽象:比如
Dog
類、Shape
類; - 哈士奇、泰迪同屬於狗,具有小狗型別的一些屬性,比如吃飯、睡覺等,三角形和正方形同屬於形狀,有相同的一些屬性,比如邊長、面積等,那麼針對於哈士奇、泰迪,我們選擇了Dog來描述,我們可以說:哈士奇、泰迪的型別是
Dog
Shape
類; - 那麼請問:
Dog
類和Shape
類的型別是什麼類?答案便是Class
類,它用來描述類的型別。
- java API中有個類
從JVM類載入過程理解Class類。
-
以上圖
Dog
類入手,建立一個Dog
類以及一個測試類。//Dog類,是用來描述各個品種的狗的的類, public class Dog { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void eat() { System.out.println("我的名字是:" + getName() + ",我開始吃飯了!"); } } class DogTest{ public static void main(String[] args) { //huskie、teddy是Dog類的具體例項。 Dog huskie = new Dog(); huskie.setName("哈士奇"); huskie.eat(); Dog teddy = new Dog(); teddy.setName("泰迪"); teddy.eat(); } }
-
過程解讀:
Dog
類經過javac.exe命令以後,會生成Dog.class
位元組碼檔案;- 之後使用java.exe對
Dog.class
位元組碼檔案進行解釋執行,即載入到記憶體中,該過程稱為類的載入; - 載入
Dog.class
類時,JVM為其建立一個Class型別的例項,並將其關聯起來。也就是說:當Dog.class
載入到記憶體中時,JVM為其建立了一個Class例項,這個Class例項包含了該class型別的所有完整資訊。那麼只要能得到某個類的Class例項,那麼便可以拿到該類的類名、包名、父類、實現的介面、所有方法、欄位等等。
注意:
- 類的載入過程中,載入到記憶體中的類,稱之為執行時類
Class
例項在JVM中是唯一的,因此無論通過何種方式獲取到的Class例項都是同一個例項。- 不同的
class
檔案會產生不同的Class
例項,載入Dog.class
時,JVM會為Dog.class
建立一個Class
型別的例項, 載入Shape.class
時,JVM會為Shape.class
建立一個Class
型別的例項。
Class例項與類的例項
-
方便個人理解,直接上圖。
-
我們對
Dog.class
位元組碼檔案進行解釋執行,載入到記憶體中,並且將這些靜態資料轉換成方法區的執行時的資料結構,之後會在堆中生成一個代表這個類的java.lang.Class
物件,用來封裝類在方法區內的資料結構,可以作為方法區中類資料的訪問入口(好像JDK版本不同,Class物件所在的位置也不同)。
反射概念及意義
基本理解
- 在上面記憶體載入時建立了
Class
例項,Class
例項上儲存了這個類的所有完整資訊,那麼,通過Class
例項獲取class
資訊的方法便稱為反射(Reflection); - 反射機制就是在程式的執行過程中被允許對程式本身進行操作,它是動態語言的關鍵;
- 在程式執行時,系統始終為所有的物件維護一個被稱為執行時的型別標識。例如
Dog.class
有Dog
類的Class
型別的例項,Shape.class
有Shape
類Class
型別的例項。這些Class型別的例項跟蹤著每個物件所屬的類。這些Class型別的例項儲存這些類的完整資訊。 - 與
class
類的關係:class是描述類的一個關鍵字。Class卻是儲存著執行時資訊的類。 Class
與反射配套使用,因為Class類能夠幫助我們在程式執行時分析類,獲取執行時類中的值。
反射能做到的事
- 拿到
Class
例項後,我們在程式執行時便可以做到:- 判斷任意一個物件所屬的類以及構造任意一個類的物件;
- 獲取任意一個類所具有的成員變數和方法;
- 獲取泛型資訊(會在泛型章節詳細分析);
- 呼叫任意一個物件的成員變數和方法;
- 處理註解;
- 動態代理。
- 可以擁有
Class
物件的型別有:- primitive type:基本資料型別;
- class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類;
- interface:介面;
- []:陣列;
- enum:列舉;
- annotation:註解@interface;
- void。
反射的常用方法
獲取Class類的例項
- 獲取Class類的例項的四種方法,以
Dog
類為例。
//方式一:直接呼叫執行時類的屬性:.class;
Class clazzOne = Dog.class;
System.out.println("方式一獲取到的Class:" + clazzOne);
//方式二:使用執行時類的物件,呼叫getClass()方法;
Dog dog1 = new Dog();
Class clazzTwo = dog1.getClass();
System.out.println("方式二獲取到的Class:" + clazzTwo);
//方式三:直接使用Class的靜態方法:forName(String classPath);
Class clazzThree = Class.forName("com.practice.reflect.Dog");
System.out.println("方式三獲取到的Class:" + clazzThree);
//方式四:使用類的載入器:ClassLoader
ClassLoader classLoader = DogTest.class.getClassLoader();
Class clazzFour = classLoader.loadClass("com.practice.reflect.Dog");
System.out.println("方式四獲取到的Class:" + clazzFour);
//比較獲取到的是否是同一個Class
Boolean bool = (clazzOne == clazzTwo) && (clazzTwo == clazzThree) && (clazzThree == clazzFour);
System.out.println(bool);
獲取類載入器
- 類載入器有如下幾種型別
- 自定義類載入器 ➡️ 系統類載入器 ➡️ 擴充套件類載入器 ➡️ 引導類載入器
- 系統類載入器(System Classloader):負責java –classpath 或 –D java.class.path所指的目錄下的類與jar包裝入工作 ,是最常用的載入器;
- 擴充套件類載入器(Extension Classloader):負責jre/lib/ext目錄下的jar包或 –D java.ext.dirs 指定目錄下的jar包裝入工作庫;
- 引導類載入器(Bootstap Classloader):用C++編寫的,是JVM自帶的類載入器,負責Java平臺核心庫,用來裝載核心類庫。該載入器無法直接獲取。
public static void main(String[] args) {
//拿到DogTest類的類載入器,自定義的類是系統類載入器進行載入
ClassLoader classLoaderOne = DogTest.class.getClassLoader();
System.out.println("系統類載入器classLoaderOne:" + classLoaderOne);
//獲取一個系統類載入器
ClassLoader classLoaderOne1 = ClassLoader.getSystemClassLoader();
System.out.println("系統類載入器classLoaderOne1:" + classLoaderOne1);
System.out.println(classLoaderOne1 == classLoaderOne);
//系統類載入器可以以流的方式建立一個資源 檔案目錄在當前src下
InputStream is = classLoaderOne1.getResourceAsStream("jdbc.properties");
//呼叫系統類載入器的getParent():獲取擴充套件類載入器
ClassLoader classLoaderTwo = classLoaderOne.getParent();
System.out.println("擴充套件類載入器classLoaderTwo:" + classLoaderTwo);
//呼叫擴充套件類載入器的getParent():無法獲取引導類載入器
//引導類載入器主要負責載入java的核心類庫,無法載入自定義類的。
ClassLoader classLoaderThree = classLoaderTwo.getParent();
System.out.println("引導類載入器classLoaderThree:" + classLoaderThree);
}
建立執行時類的物件
-
獲取到Class類的例項後,我們可以建立執行時類的物件;比如通過Dog.class獲取到對應的Class例項後,建立一個Dog物件名為Labrador拉布拉多犬。
//通過Dog.class拿到Class物件後 Class clazz = Dog.class; //使用Class的newInstance()方法建立物件,它實際上是呼叫了執行時類的空參的構造器 //使用條件: 1.執行時類必須提供空參的構造器 2.空參的構造器的訪問許可權得夠,通常為public。 Dog Labrador = (Dog)clazz.newInstance(); //也可以通過構造器建立 有這樣的構造器:Dog(String name) Constructor constructor = clazz.getConstructor(String.class); Dog d = (Dog)constructor.newInstance("Labrador");
反射各方法梳理
類的屬性
-
首先拿到
Class
例項。Class clazz = Dog.class;
-
通過Class拿到類的屬性
//獲取當前執行時類及其父類中宣告為public訪問許可權的屬性 Field[] fields = clazz.getFields(); //獲取當前執行時類中宣告的所有屬性。(不包含父類中宣告的屬性) Field[] declaredFields = clazz.getDeclaredFields();
-
通過上面拿到的屬性進而拿到這個屬性的其他資訊
getModifiers()
:返回屬性的修飾符,是一個int
;getType()
:返回欄位型別,也是一個Class
例項;getName()
:返回屬性名稱;
Field[] fields1 = clazz.getDeclaredFields(); for (Field field : fields1) { //以 private String name; 為例 //返回許可權修飾符 private int m = field.getModifiers(); System.out.print(Modifier.toString(m) + "\t"); //返回該欄位的型別 String Class s = field.getType(); System.out.print(s.getName() + "\t"); //返回該欄位的名字 name String name = field.getName(); System.out.print(name + "\t"); }
-
拿到指定屬性的值,並對指定屬性的值進行操作
Dog teddy = new Dog(); teddy.setName("泰迪"); Class c = teddy.getClass(); Field field = c.getDeclaredField("name"); //如果不設定 會報錯:IllegalAccessException 因為name的屬性是private的。 field.setAccessible(true); Object value = field.get(teddy); System.out.println("name屬性的值為" + value); //設定name的值 第一個引數是哪一個類的例項,第二個引數是要設定的值。 field.set(teddy, "哈士奇"); System.out.println("修改後的值為" + teddy.getName());
-
方法總結
Field[] getFields()
:獲取所有public的field(包括父類);Field[] getDeclaredFields()
:獲取當前類的所有field(不包括父類);Field getField(name)
:根據欄位名獲取某個public的field(包括父類);Field getDeclaredField(name)
:根據欄位名獲取當前類的某個field(不包括父類)。
類的方法
-
通過
Class
例項可以獲取到方法Method
資訊//getMethods():獲取當前執行時類及其所有父類中宣告為public許可權的方法 Method[] methods = clazz.getMethods(); //getDeclaredMethods():獲取當前執行時類中宣告的所有方法。(不包含父類中宣告的方法) Method[] methods1 = clazz.getDeclaredMethods(); //獲取某個public的Method(包括父類) Method method1 = clazz.getMethod("play"); //獲取當前類的某個Method(不包括父類) 無參 Method method = clazz.getDeclaredMethod("sleep");
-
通過上面拿到的方法,可以進一步拿到該方法的其他資訊。
getAnnotations()
:獲取方法宣告的註解;getModifiers()
:返回方法的修飾符,它是一個int
,不同的bit表示不同的含義;getReturnType()
:返回方法返回值型別;getName()
:返回方法名稱;getParameterTypes()
:返回方法的引數型別,是一個Class陣列;getExceptionTypes()
:丟擲的異常。
//getDeclaredMethods():獲取當前執行時類中宣告的所有方法。(不包含父類中宣告的方法) Method[] methods1 = clazz.getDeclaredMethods(); for (Method method : methods1) { //一、獲取該方法宣告的註解 Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { System.out.println("方法的註解為:" + annotation); } //二、獲取許可權修飾符 System.out.print(Modifier.toString(method.getModifiers()) + "\t"); //三、獲取返回值型別 System.out.print(method.getReturnType().getName() + "\t"); //四、獲取方法名 System.out.printf(method.getName()); //五、獲取引數列表 Class[] parameterTypes = method.getParameterTypes(); //六、獲取丟擲的異常 Class[] exceptionTypes = method.getExceptionTypes(); }
-
呼叫方法
Object invoke(Object instance, Object... parameters)
呼叫某個物件的方法,第一個引數是物件例項,即在哪一個物件上呼叫該方法;第二個引數是引數列表。
Class clazz = Dog.class; //建立執行時類的物件 Dog dog1 = (Dog)clazz.newInstance(); //以該方法為例:public String play(String toy),獲取指定的方法 Method method = clazz.getDeclaredMethod("play",String.class); //確保當前方法可以訪問 可以呼叫private修飾的方法, //不加的話,private方法會報錯:IllegalAccessException method.setAccessible(true); //呼叫方法:呼叫方法的invoke,Object invoke(Object instance, Object... parameters) // 引數1:物件例項-->dog1 引數2:形參列表; 呼叫靜態方法時,第一個引數傳null。 //方法的返回值為呼叫方法的返回值 Object returnObj = method.invoke(dog1, "籃球"); System.out.println(returnObj);
類的構造器
- 通過Class物件,可以拿到當前執行時類的構造資訊
getConstructor(Class...)
:獲取當前執行類中某個public
的Constructor
;getDeclaredConstructor(Class...)
:獲取當前執行類中某個Constructor
;getConstructors()
:獲取當前執行類中所有public
的Constructor
;getDeclaredConstructors()
:獲取當前執行類中所有Constructor
。
//Dog類的例項化
//1、使用new關鍵字
Dog teddy = new Dog();
teddy.setName("teddy");
//2、使用反射的方式
//2.1:使用的是無參的構造方法;2.2:許可權修飾符為public
Dog dog1 = Dog.class.newInstance();
dog1.setName("田園犬");
//3、使用反射呼叫任意的構造方法 例:public Dog(String name);
Class clazz = Dog.class;
//3.1、取當前執行時類中宣告為public的構造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println();
//3.2、獲取當前執行類中宣告的所有構造器的方法
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//3.3、獲取當前執行類中的String型別引數的構造方法
Constructor constructor = clazz.getConstructor(String.class);
//3.4、保證此構造器是可訪問的
constructor.setAccessible(true);
//3.5、呼叫構造方法建立物件
Dog dog2 = (Dog)constructor.newInstance("哈士奇");
獲取類的其他資訊
- 除下以上常用的方法之外,還可以通過Class例項獲取該類的其他資訊
Class getSuperclass()
獲取執行時的父類;Type getGenericSuperclass()
獲取帶泛型父類;Type[] getActualTypeArguments()
獲取帶泛型的父類的泛型;
Class[] getInterfaces()
:獲取當前類實現的所有介面;Package getPackage()
:獲取當前類所在包;Annotation[] getAnnotations()
:獲取執行時類宣告的註解。
總結
- 反射是框架的靈魂,學好反射才能深入理解框架。
原創不易,歡迎轉載,轉載時請註明出處,謝謝!
作者:瀟~蕭下
原文連結:https://www.cnblogs.com/manongxiao/p/13429889.html