20 Java 使用反射(Reflection) 和內(nèi)省技術(shù)

反射(Reflection)是程序的自我分析能力,通過反射可以確定類有哪些方法、有哪些構(gòu)造方法以及有哪些成員變量。Java語言提供了反射機制,通過反射機制能夠動態(tài)讀取一個類的信息;能夠在運行時動態(tài)加載類,而不是在編譯期。反射可以應(yīng)用于框架開發(fā),它能夠從配置文件中讀取配置信息動態(tài)加載類、創(chuàng)建對象,以及調(diào)用方法和成員變量。

Java反射機制API

Java反射機制API主要是 java.lang.Class 類和 java.lang.reflect 包。

java.lang.Class類

java.lang.Class類是實現(xiàn)反射的關(guān)鍵所在,Class類的一個實例表示Java的一種數(shù)據(jù)類型,包括類、接口、枚舉、注解(Annotation)、數(shù)組、基本數(shù)據(jù)類型和void,void是“無類型”,主要用于方法返回值類型聲明,表示不需要返回值。Class沒有公有的構(gòu)造方法,Class實例是由JVM在類加載時自動創(chuàng)建的

方法1:調(diào)用Object類的getClass()方法。
方法2:使用Class類的forName()方法。
方法3:如果T是一個Java類型,那么T.class就代表了與該類型匹配的Class對象。

    public static void main( String[] args ) throws ClassNotFoundException {
        // 1.通過類型class靜態(tài)變量
        Class clazz;
        clazz = int.class;
        print(clazz);

        String[] array = new String[1];
        // 2.通過對象的getClass()方法
        clazz = array.getClass();
        print(clazz);

        clazz = Class.forName("java.lang.Override");
        print(clazz);
    }

    private static void print(Class clazz) {
        System.out.println("ClassName = " + clazz.getName());
        System.out.println("isAnnotation = " + clazz.isAnnotation());
        System.out.println("isArray = " + clazz.isArray());
        System.out.println("isPrimitive = " + clazz.isPrimitive());
        System.out.println("isEnum = " + clazz.isEnum());
        System.out.println("isInterface = " + clazz.isInterface());
        System.out.println("---------------------");
    }

每一種類型包括類和接口等,都有一個class靜態(tài)變量可以獲得Class實例。另外,每一個對象都有g(shù)etClass()方法可以獲得Class實例,該方法是由Object類提供的實例方法。

是否為接口: isInterface()
是否為數(shù)組對象: isArray()
是否是基本類型: isPrimitive()
獲得父類: class: getSuperclass()

java.lang.reflect包

java.lang.reflect包提供了反射中用到類,主要的類說明如下:

  • Constructor類:提供類的構(gòu)造方法信息。
  • Field類:提供類或接口中成員變量信息。
  • Method類:提供類或接口成員方法信息。
  • Array類:提供了動態(tài)創(chuàng)建和訪問Java數(shù)組的方法。
  • Modifier類:提供類和成員訪問修飾符信息。

示例代碼如下:

    public static void main(String[] args) {

        try {
            // 動態(tài)加載xx類的運行時對象
            Class c = Class.forName("java.lang.String");      
            // 獲取成員方法集合
            Method[] methods = c.getDeclaredMethods();        
            // 遍歷成員方法集合
            for (Method method : methods) {                   
                // 打印權(quán)限修飾符,如public、protected、private
                System.out.print(Modifier.toString(method.getModifiers()));        
                // 打印返回值類型名稱
                System.out.print(" " + method.getReturnType().getName() + " ");    
                // 打印方法名稱
                System.out.println(method.getName() + "();");                      
            }
        } catch (ClassNotFoundException e) {                                       
            System.out.println("找不到指定類");
        }
    }
  • 通過Class的靜態(tài)方法forName(String)創(chuàng)建某個類的運行時對象,其中的參數(shù)是類全名字符串,如果在類路徑中找不到這個類則拋出ClassNotFoundException異常。
  • 通過Class的實例方法getDeclaredMethods()返回某個類的成員方法對象數(shù)組。
  • method.getModifiers()方法返回訪問權(quán)限修飾符常量代碼,是int類型,例如1代表public,這些數(shù)字代表的含義可以通過Modifier.toString(int)方法轉(zhuǎn)換為字符串。
  • 通過Method的getReturnType()方法獲得方法返回值類型,然后再調(diào)用getName()方法返回該類型的名稱。
  • method.getName()返回方法名稱。

創(chuàng)建對象

反射機制提供了另外一種創(chuàng)建對象方法,Class類提供了一個實例方法newInstance(),通過該方法可以創(chuàng)建對象。

下面兩條語句實現(xiàn)了創(chuàng)建字符串String對象。

Class clz = Class.forName("java.lang.String");
String str = (String) clz.newInstance();

這兩條語句相當于String str = new String()語句。另外,需要注意newInstance()方法有可以會拋出

  • InstantiationException表示不能實例化異常
  • IllegalAccessException是不能訪問構(gòu)造方法異常。

調(diào)用構(gòu)造方法

調(diào)用方法newInstance()創(chuàng)建對象,這個過程中需要調(diào)用構(gòu)造方法,上面的代碼只是調(diào)用了String的默認構(gòu)造方法。如果想要調(diào)用非默認構(gòu)造方法,需要使用Constructor對象,它對應(yīng)著一個構(gòu)造方法,獲得Constructor對象需要使用Class類的如下方法:

  • Constructor[] getConstructors():返回所有公有構(gòu)造方法Constructor對象數(shù)組。
  • Constructor[] getDeclaredConstructors():返回所有構(gòu)造方法Constructor對象數(shù)組。
  • Constructor getConstructor(Class... parameterTypes):根據(jù)參數(shù)列表返回一個公有Constructor對象。參數(shù)parameterTypes是Class數(shù)組,指定構(gòu)造方法的參數(shù)列表。
  • Constructor getDeclaredConstructor(Class... parameterTypes):根據(jù)參數(shù)列表返回一個Constructor對象。參數(shù)parameterTypes同上。
private static void printConstructor() throws Exception {
        Class clazz = String.class;
        System.out.println(clazz.newInstance());

        Class<?> [] parameterTypes = {String.class};
        final Constructor constructor = clazz.getConstructor(parameterTypes);
        Object[] initArgs = {"666"};
        String str = (String) constructor.newInstance(initArgs);
        System.out.println(str);
    }

Java反射機制能夠在運行時動態(tài)加載類,而不是在編譯期。在一些框架開發(fā)中經(jīng)常將要實例化的類名保存到配置文件中,在運行時從配置文件中讀取類名字符串,然后動態(tài)創(chuàng)建對象,建立依賴關(guān)系。采用new創(chuàng)建對象依賴關(guān)系是在編譯期建立的,反射機制能夠?qū)⒁蕾囮P(guān)系推遲到運行時建立,這種依賴關(guān)系動態(tài)注入進來稱為依賴注入。

調(diào)用方法

通過反射機制還可以調(diào)用方法,這與調(diào)用構(gòu)造方法類似。調(diào)用方法需要使用Method對象,它對應(yīng)著一個方法,獲得Method對象需要使用Class類的如下方法:

  • Method[] getMethods():返回所有公有方法Method對象數(shù)組。
  • Method[] getDeclaredMethods():返回所有方法Method對象數(shù)組。
  • Method getMethod(String name, Class... parameterTypes):通過方法名和參數(shù)類型返回公有方法Method對象。參數(shù)parameterTypes是Class數(shù)組,指定方法的參數(shù)列表。
  • Method getDeclaredMethod(String name, Class... parameterTypes):通過方法名和參數(shù)類型返回方法Method對象。參數(shù)parameterTypes同上。
private static void testMethods() throws Exception {
        // 正常實現(xiàn)
        final LocalDate now = LocalDate.now();
        System.out.println(now);

        final LocalDate date = LocalDate.of(2020, 1, 2);
        System.out.println(date);

        System.out.println(date.getYear());

        // 使用 method 方式
        final Class<LocalDate> clazz = LocalDate.class;
        // 調(diào)用靜態(tài)方法
        Object obj = clazz.getMethod("now").invoke(null);
        LocalDate localDate  = (LocalDate) obj;
        System.out.println(obj);
        // 調(diào)用靜態(tài)方法的有參方法
        Class<?> [] parameterTypes = {int.class, int.class, int.class};
        Object[] args = {2002, 1, 2};
        obj = clazz.getMethod("of", parameterTypes).invoke(null, args);
        System.out.println(obj);
        // 調(diào)用實例的無參方法
        obj = clazz.getMethod("getYear").invoke(localDate);
        System.out.println(obj);
    }

調(diào)用成員變量

通過反射機制還可以調(diào)用成員變量,調(diào)用方法需要使用Field對象,它對應(yīng)著一個方法,獲得Field對象需要使用Class類的如下方法:

  • Field[] getFields():返回所有公有成員變量Field對象數(shù)組。
  • Field[] getDeclaredFields():返回所有成員變量Field對象數(shù)組。
  • Field getField(String name):通過指定公共成員變量名返回Field對象。
  • Field getDeclaredField(String name):通過指定成員變量名返回Field對象。
private static void testFiled() throws Exception {
        // 已知 Exception 有一個成員變量 private String detailMessage;
        Throwable exception = new Throwable("hello");
        System.out.println(exception.getMessage());

        final Class<? extends Throwable> aClass = exception.getClass();
        // 獲取非靜態(tài)方法的字段
        final Field detailMessageField = aClass.getDeclaredField("detailMessage");
        detailMessageField.setAccessible(true);
        // 非靜態(tài)的 get
        final Object obj = detailMessageField.get(exception);
        System.out.println(obj);
        // 非靜態(tài)的 set
        detailMessageField.set(exception, "new value");
        System.out.println(exception.getMessage());

        // 獲取靜態(tài)字段的值
        // private static final long serialVersionUID = -3042686055658047285L;
        final Field serialVersionUIDField = aClass.getDeclaredField("serialVersionUID");
        serialVersionUIDField.setAccessible(true);
        System.out.println(serialVersionUIDField.get(null));
    }

輸出

hello
hello
new value
-3042686055658047285

設(shè)置成員變量accessible標志為true,accessible是可訪問性標志,值為 true 則指示反射的對象在使用時應(yīng)該取消Java語言訪問檢查。值為false則指示反射的對象應(yīng)該實施Java語言訪問檢查。不僅是成員變量,方法和構(gòu)造方法也可以通過setAccessible(true)設(shè)置,實現(xiàn)對私有方法和構(gòu)造方法的訪問

拓展

1.Type[] java.lang.Class.getGenericInterfaces()
2.Class<?>[] java.lang.Class.getInterfaces()

  • 獲取類的接口實現(xiàn)信息
    1.返回實現(xiàn)接口信息的Type數(shù)組,包含泛型信息
    2.返回實現(xiàn)接口信息的Class數(shù)組,不包含泛型信息

細看一下,就會發(fā)現(xiàn)其中端倪,當你的實現(xiàn)接口中不包含泛型時,同樣調(diào)用1方法,其返回的接口信息必然不帶泛型信息的,也就是1中包含2。

如何拿到接口中定義的泛型Person?

    public static void main(String[] args) {
        Class clz = new MyInterface<Person>() {
            @Override
            public Person get() {
                return null;
            }
        }.getClass();
        // 如何拿到接口中定義的泛型Person
        System.out.println(((ParameterizedType) clz.getGenericInterfaces()[0]).getActualTypeArguments()[0]);
        
        clz = new MyAbstractClass<Person>() {
            @Override
            public Person get() {
                return null;
            }
        }.getClass();
        // 如何拿到抽象類中定義的泛型Person
        System.out.println(((ParameterizedType) clz.getGenericSuperclass()).getActualTypeArguments()[0]);
        
        
    }

    static interface MyInterface<T> {
        T get();
    }
    
    static abstract class MyAbstractClass<T> {
        abstract T get();
    }

內(nèi)省技術(shù)

PropertyDescriptor類表示JavaBean類通過存儲器導出一個屬性。主要方法:

1、getPropertyType(),獲得屬性的Class對象。
2、getReadMethod(),獲得用于讀取屬性值的方法;getWriteMethod(),獲得用于寫入屬性值的方法。
3、hashCode(),獲取對象的哈希值。
4、setReadMethod(Method readMethod),設(shè)置用于讀取屬性值的方法;setWriteMethod(Method writeMethod),設(shè)置用于寫入屬性值的方法;
將JavaBean中的屬性封裝起來進行操作。在程序把一個類當做JavaBean來看,就是調(diào)用Introspector.getBeanInfo()方法,得到的BeanInfo對象封裝了把這個類當做JavaBean看的結(jié)果信息,即屬性的信息。需要導包java.beans.*。

private static void testPropertyDescriptor() throws Exception {
        // 使用內(nèi)省
        final Class<App> appClass = App.class;
        App app = appClass.newInstance();
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor("sss", appClass);
        Method writeMethod = propertyDescriptor.getWriteMethod();
        writeMethod.invoke(app, "11111");
        System.out.println(propertyDescriptor.getReadMethod().invoke(app));

        BeanInfo beanInfo = Introspector.getBeanInfo(appClass);
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            System.out.println(pd);
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,779評論 18 399
  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,186評論 6 13
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 4,637評論 2 12
  • 厚實純棉外套 側(cè)面 背后披肩 正面 針織打底 小碎花外套 正面 上衣:針織打底 褲子:破洞
    就叫課余好了閱讀 158評論 0 0