1. 程式人生 > >面試題之------Java 反射機制

面試題之------Java 反射機制

orange 是不是 rac 匿名對象 結果 創建 一起 使用 edi


一、反射機制概述

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 反射機制