面試題之------Java 反射機制
一、反射機制概述
Java 反射機制是在運行狀態中,對於任意一個類,都能夠獲得這個類的所有屬性和方法,對於任意一個對象都能夠調用它的任意一個屬性和方法。這種在運行時動態的獲取信息以及動態調用對象的方法的功能稱為Java 的反射機制。
Class 類與java.lang.reflect 類庫一起對反射的概念進行了支持,該類庫包含了Field,Method,Constructor類(每個類都實現了Member 接口)。這些類型的對象時由JVM 在運行時創建的,用以表示未知類裏對應的成員。
這樣你就可以使用Constructor 創建新的對象,用get() 和set() 方法讀取和修改與Field 對象關聯的字段,用invoke() 方法調用與Method 對象關聯的方法。另外,還可以調用getFields() getMethods() 和 getConstructors() 等很便利的方法,以返回表示字段,方法,以及構造器的對象的數組。這樣匿名對象的信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情。
二、獲取字節碼的方式
在Java 中可以通過三種方法獲取類的字節碼(Class)對象
- 通過Object 類中的getClass() 方法,想要用這種方法必須要明確具體的類並且創建該類的對象。
- 所有數據類型都具備一個靜態的屬性.class 來獲取對應的Class 對象。但是還是要明確到類,然後才能調用類中的靜態成員。
- 只要通過給定類的字符串名稱就可以獲取該類的字節碼對象,這樣做擴展性更強。通過Class.forName() 方法完成,必須要指定類的全限定名,由於前兩種方法都是在知道該類的情況下獲取該類的字節碼對象,因此不會有異常,但是Class.forName() 方法如果寫錯類的路徑會報 ClassNotFoundException 的異常。
package com.jas.reflect;
public class ReflectTest {
public static void main(String[] args) {
Fruit fruit = new Fruit();
Class<?> class1 = fruit.getClass(); //方法一
Class<?> class2 = Fruit.class; //方法二
Class class3 = null;
try { //方法三,如果這裏不指定類所在的包名會報 ClassNotFoundException 異常
class3 = Class.forName("com.jas.reflect.Fruit");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(class1 + " " +class2 + " " + class3);
}
}
class Fruit{}
三、通過反射機制獲取類信息
通過反射機制創建對象,在創建對象之前要獲得對象的構造函數對象,通過構造函數對象創建對應類的實例。
下面這段代碼分別在運行期間創建了一個無參與有參的對象實例。由於getConstructor() 方法與newInstance() 方法拋出了很多異常(你可以通過源代碼查看它們),這裏就簡寫了直接拋出一個Exception,下同。
package com.jas.reflect;
import java.lang.reflect.Constructor;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class clazz = null;
clazz = Class.forName("com.jas.reflect.Fruit");
Constructor<Fruit> constructor1 = clazz.getConstructor();
Constructor<Fruit> constructor2 = clazz.getConstructor(String.class);
Fruit fruit1 = constructor1.newInstance();
Fruit fruit2 = constructor2.newInstance("Apple");
}
}
class Fruit{
public Fruit(){
System.out.println("無參構造器Run...........");
}
public Fruit(String type){
System.out.println("有參構造器Run..........." + type);
}
}
輸出:
無參構造器Run………..
有參構造器Run………..Apple
通過反射機制獲取Class 中的屬性。
package com.jas.reflect;
import java.lang.reflect.Field;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class<?> clazz = null;
Field field = null;
clazz = Class.forName("com.jas.reflect.Fruit");
//field = clazz.getField("num"); getField() 方法不能獲取私有的屬性
// field = clazz.getField("type"); 訪問私有字段時會報 NoSuchFieldException異常
field = clazz.getDeclaredField("type"); //獲取私有type 屬性
field.setAccessible(true); //對私有字段的訪問取消檢查
Fruit fruit = (Fruit) clazz.newInstance(); //創建無參對象實例
field.set(fruit,"Apple"); //為無參對象實例屬性賦值
Object type = field.get(fruit); //通過fruit 對象獲取屬性值
System.out.println(type);
}
}
class Fruit{
public int num;
private String type;
public Fruit(){
System.out.println("無參構造器Run...........");
}
public Fruit(String type){
System.out.println("有參構造器Run..........." + type);
}
}
輸出:
無參構造器Run………..
Apple
通過反射機制獲取Class 中的方法並運行。
package com.jas.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Class clazz = null;
Method method = null;
clazz = Class.forName("com.jas.reflect.Fruit");
Constructor<Fruit> fruitConstructor = clazz.getConstructor(String.class);
Fruit fruit = fruitConstructor.newInstance("Apple"); //創建有參對象實例
method = clazz.getMethod("show",null); //獲取空參數show 方法
method.invoke(fruit,null); //執行無參方法
method = clazz.getMethod("show",int.class); //獲取有參show 方法
method.invoke(fruit,20); //執行有參方法
}
}
class Fruit{
private String type;
public Fruit(String type){
this.type = type;
}
public void show(){
System.out.println("Fruit type = " + type);
}
public void show(int num){
System.out.println("Fruit type = " + type + ".....Fruit num = " + num);
}
}
輸出:
Fruit type = Apple
Fruit type = Apple…..Fruit num = 20
四、反射機制簡單應用(使用簡單工廠創建對象)
Class.forName() 生成的結果是在編譯時不可知的,因此所有的方法特征簽名信息都是在執行時被提取出來的。反射機制能過創建一個在編譯期完全未知的對象,並調用該對象的方法。
以下是反射機制與泛型的一個應用,通過一個工廠類創建不同類型的實例。
要創建對象的實例類Apple :
package com.jas.reflect;
public interface Fruit {}
class Apple implements Fruit{}
加載的配置文件config.properties:
Fruit=com.jas.reflect.Apple
工廠類BasicFactory :
package com.jas.reflect;
import java.io.FileReader;
import java.util.Properties;
public class BasicFactory {
private BasicFactory(){}
private static BasicFactory bf = new BasicFactory();
private static Properties pro = null;
static{
pro = new Properties();
try{
//通過類加載器加載配置文件
pro.load(new FileReader(BasicFactory.class.getClassLoader().
getResource("config.properties").getPath()));
}catch (Exception e) {
e.printStackTrace();
}
}
public static BasicFactory getFactory(){
return bf;
}
//使用泛型獲得通用的對象
public <T> T newInstance(Class<T> clazz){
String cName = clazz.getSimpleName(); //獲得字節碼對象的類名
String clmplName = pro.getProperty(cName); //根據字節碼對象的類名通過配置文件獲得類的全限定名
try{
return (T)Class.forName(clmplName).newInstance(); //根據類的全限定名創建實例對象
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
創建對象實例:
package com.jas.reflect;
public class ReflectTest {
public static void main(String[] args) throws Exception {
Fruit fruit = BasicFactory.getFactory().newInstance(Fruit.class);
System.out.println(fruit);
}
}
輸出
com.jas.reflect.Apple@4554617c
上面這個實例通過一個工廠創建不同對象的實例,通過這種方式可以降低代碼的耦合度,代碼得到了很大程度的擴展,以前要創建Apple 對象需要通過new 關鍵字創建Apple 對象,如果我們也要創建Orange 對象呢?是不是也要通過new 關鍵字創建實例並向上轉型為Fruit ,這樣做是麻煩的。
現在我們直接有一個工廠,你只要在配置文件中配置你要創建對象的信息,你就可以創建任何類型你想要的對象,是不是簡單很多了呢?可見反射機制的價值是很驚人的。
Spring 中的 IOC 的底層實現原理就是反射機制,Spring 的容器會幫我們創建實例,該容器中使用的方法就是反射,通過解析xml文件,獲取到id屬性和class屬性裏面的內容,利用反射原理創建配置文件裏類的實例對象,存入到Spring的bean容器中。
參考書籍:
《Java 編程思想》 Bruce Eckel 著 陳昊鵬 譯
面試題之------Java 反射機制