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();

這兩條語句相當(dāng)于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的默認(rèn)構(gòu)造方法。如果想要調(diào)用非默認(rèn)構(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)注入進(jìn)來稱為依賴注入。

調(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標(biāo)志為true,accessible是可訪問性標(biāo)志,值為 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ù)組,不包含泛型信息

細(xì)看一下,就會發(fā)現(xiàn)其中端倪,當(dāng)你的實現(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類通過存儲器導(dǎo)出一個屬性。主要方法:

1、getPropertyType(),獲得屬性的Class對象。
2、getReadMethod(),獲得用于讀取屬性值的方法;getWriteMethod(),獲得用于寫入屬性值的方法。
3、hashCode(),獲取對象的哈希值。
4、setReadMethod(Method readMethod),設(shè)置用于讀取屬性值的方法;setWriteMethod(Method writeMethod),設(shè)置用于寫入屬性值的方法;
將JavaBean中的屬性封裝起來進(jìn)行操作。在程序把一個類當(dāng)做JavaBean來看,就是調(diào)用Introspector.getBeanInfo()方法,得到的BeanInfo對象封裝了把這個類當(dāng)做JavaBean看的結(jié)果信息,即屬性的信息。需要導(dǎo)包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ù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,347評論 2 377

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

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