一、概述
什麼是反射?
Java反射機制指的是在Java程序運行狀態中,對於任何一個類,都可以獲得這個類的所有屬性和方法;對於給定的一個對象,都能夠調用它的任意一個屬性和方法。
這種動態獲取類的內容以及動態調用對象的方法稱爲反射機制。
爲什麼使用反射?
在計算機科學領域,反射是指一類能夠自我描述和自控制的應用。
在Java編程語言中,反射是一種強有力的工具,是面向抽象編程的一種實現方式,它能使代碼語句更加靈活,極大提高代碼的運行時裝配能力。Java反射機制允許編程人員在對類未知的情況下,獲取類相關信息的方式變得更加多樣靈活,調用類中相應方法,是Java增加其靈活性與動態性的一種機制。
總結一下,Java反射機制有如下作用:
- 反射機制極大的提高了程序的靈活性和擴展性,降低模塊的耦合性,提高自身的適應能力;
- 通過反射機制可以讓程序創建和控制任何類的對象,無需提前硬編碼目標類;
- 使用反射機制能夠在運行時構造一個類的對象、判斷一個類所具有的成員變量和方法、調用一個對象的方法;
- 反射機制是構建框架技術的基礎所在,使用反射可以避免將代碼寫死在框架中。
二、反射的原理
我們知道了什麼是反射以及反射的作用,那麼在Java中是如何支持反射的呢?
首先我們需要了解Java程序運行的過程,該過程包含兩個階段:編譯和運行。
在程序編譯階段,Java代碼會通過JDK編譯成 .class字節碼文件;
在程序運行階段,JVM會去調用業務邏輯對應需要的的字節碼文件,生成對應的Class對象,並調用其中的屬性方法完成業務邏輯。
Java的反射機制原理就是在程序運行階段,主動讓JVM去加載某個 .class文件生成Class對象,並調用其中的方法和屬性。 如下圖:
注:
- Class類在java.lang包中,繼承了Object;
- Class對象的由來是將.class文件讀入內存,併爲之創建一個Class對象,一個.class文件對應一個Class對象;
三、反射的使用
通過第二節的原理描述,我們知道使用Java反射時,有一個息息相關的類——Class類,其實不單單是Class類,還有三個主要使用的類,如下表:
下面我們來一一介紹:
1、獲取Java類(Class類的使用)
這是使用Java反射的第一步,獲取了對應的Class類之後,我們就可以生成對象,然後調用對象的方法和屬性,這一步有三種實現方式。
方法一:
Book book = new Book();
Class bookClass = book.getClass();
//輸出類名
System.out.println(bookClass.getName());
這個方法其實是Object的一個方法,Class繼承了Object,所以我們可以直接使用。
方法二:
Class bookClass = Book.class;
//輸出類名
System.out.println(bookClass.getName());
方法三:
Class bookClass;
try {
bookClass = Class.forName("test.Book");
//輸出類名
System.out.println(bookClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
方法三是使用最多的方法。
2、獲取類的構造函數(Constructor類的使用)
我們獲取到一個類的Class對象之後,可以調用Class對象的getDeclaredConstructors()方法獲取該類的構造函數,如下:
// 反射所有聲明的構造方法
public static void reflectAllConstructor() {
System.out.println(TAG + "=============獲取所有的聲明的構造函數==============");
try {
Class<?> classBook = Class.forName("test.Book");
Constructor<?>[] constructorsBook = classBook
.getDeclaredConstructors();
for (Constructor constructor : constructorsBook) {
System.out.println(TAG + constructor);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
獲取了構造函數之後,調用Constructor類對象的newInstance()即可構造出我們想要類的對象,如下:
Book book = (Book)constructor.newInstance();
3、獲取類的函數(Method類的使用)
當我們得到了一個Class對象之後,我們可以獲取該類的所有方法,如下:
// 反射所有的public的函數
public static void reflectPublicMethods() {
System.out.println(TAG + "=============獲取所有的public的函數==============");
try {
Class<?> classBook = Class.forName("test.Book");
Method[] methodsBook = classBook.getMethods();
for (Method method : methodsBook) {
System.out.println(TAG + method);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射所有的聲明的方法
public static void reflectAllMethods() {
System.out.println(TAG + "=============獲取所有的聲明的函數==============");
try {
Class<?> classBook = Class.forName("test.Book");
Method[] methodsBook = classBook.getDeclaredMethods();
for (Method method : methodsBook) {
System.out.println(TAG + method);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
getDeclaredMethods()和getMethods()都可以獲取到類的方法,那麼他們有什麼區別呢?
getMethods()執行結果如下:
我們看到 getMethods()不僅只獲取了自己定義的公用方法(private獲取不了),還把Object父類的公用方法也獲取了。
getDeclaredMethods()執行結果如下:
我們看到 getDeclaredMethods()只能獲取自己類中定義的方法,但是可以獲取到private方法。
4、獲取類的屬性(Field類的使用)
當我們得到了一個Class對象之後,我們可以獲取該類的所有屬性,代碼如下:
// 反射所有的public的屬性
public static void reflectPublicFields() {
System.out.println(TAG + "=============獲取所有的public的屬性==============");
try {
Class<?> classBook = Class.forName("test.Book");
Field[] fieldsBook = classBook.getFields();
for (Field field : fieldsBook) {
System.out.println(TAG + field);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射所有的聲明的屬性
public static void reflectAllFields() {
System.out.println(TAG + "=============獲取所有的聲明的屬性==============");
try {
Class<?> classBook = Class.forName("test.Book");
Field[] fieldsBook = classBook.getDeclaredFields();
for (Field field : fieldsBook) {
System.out.println(TAG + field);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
同Methods,獲取屬性也有getDeclaredFields()和getFields()兩種。
四、反射帶來的問題
反射雖然能夠給我帶來諸多便利,但是反射有一個致命問題:反射的效率比直接調用低很多,所以我們要慎用反射。
至於爲什麼反射效率低?
其實很好理解,使用反射時要通知JVM加載.class文件並且生成Class對象,而直接調用則不用,所以反射效率低。