面試題思考:什麼是 Java 的反射機制

1、反射機制概述

Java 反射機制是在運行狀態中,對於任意一個類,都可以得到這個類的全部屬性和方法,對於任意一個對象都可以調用它的任意一個屬性和方法。這種在運行時動態的獲取信息以及動態調用對象的方法的功能稱爲Java 的反射機制。java

Class 類與java.lang.reflect 類庫一塊兒對反射的概念進行了支持,該類庫包含了Field,Method,Constructor類(每一個類都實現了Member 接口)。這些類型的對象時由JVM 在運行時建立的,用以表示未知類裏對應的成員。編程

這樣你就可使用Constructor 建立新的對象,用get() 和set() 方法讀取和修改與Field 對象關聯的字段,用invoke() 方法調用與Method 對象關聯的方法。另外,還能夠調用getFields() getMethods() 和 getConstructors() 等很便利的方法,以返回表示字段,方法,以及構造器的對象的數組。這樣匿名對象的信息就能在運行時被徹底肯定下來,而在編譯時不須要知道任何事情。數組

2、獲取字節碼的方式

在Java 中能夠經過三種方法獲取類的字節碼(Class)對象函數

  • 經過Object 類中的getClass() 方法,想要用這種方法必需要明確具體的類而且建立該類的對象。
  • 全部數據類型都具有一個靜態的屬性.class 來獲取對應的Class 對象。可是仍是要明確到類,而後才能調用類中的靜態成員。
  • 只要經過給定類的字符串名稱就能夠獲取該類的字節碼對象,這樣作擴展性更強。經過Class.forName() 方法完成,必需要指定類的全限定名,因爲前兩種方法都是在知道該類的狀況下獲取該類的字節碼對象,所以不會有異常,可是Class.forName() 方法若是寫錯類的路徑會報 ClassNotFoundException 的異常。
    ackage 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{}

 

3、經過反射機制獲取類信息

經過反射機制建立對象,在建立對象以前要得到對象的構造函數對象,經過構造函數對象建立對應類的實例。ui

下面這段代碼分別在運行期間建立了一個無參與有參的對象實例。因爲getConstructor() 方法與newInstance() 方法拋出了不少異常(你能夠經過源代碼查看它們),這裏就簡寫了直接拋出一個Exception,下同。this

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 中的屬性spa

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 中的方法並運行。code

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

4、反射機制簡單應用(使用簡單工廠建立對象)

Class.forName() 生成的結果是在編譯時不可知的,所以全部的方法特徵簽名信息都是在執行時被提取出來的。反射機制能過建立一個在編譯期徹底未知的對象,並調用該對象的方法。xml

如下是反射機制與泛型的一個應用,經過一個工廠類建立不一樣類型的實例。對象

要建立對象的實例類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 著 陳昊鵬 譯

相關文章
相關標籤/搜索