36.Java反射機制

反射機制是Java動態性之一,而說到動態性首先得了解動態語言。那么何為動態語言?

一、動態語言


動態語言,是指程序在運行時可以改變其結構:新的函數可以引進,已有的函數可以被刪除等結構上的變化。比如常見的JavaScript就是動態語言,除此之外Ruby,Python等也屬于動態語言,而C、C++則不屬于動態語言。

二、Java是動態語言嗎?


從動態語言能在運行時改變程序結構結構或則變量類型上看,Java和C、C++一樣都不屬于動態語言。
但是JAVA卻又一個非常突出的與動態相關的機制:反射機制。Java通過反射機制,可以在程序運行時加載,探知和使用編譯期間完全未知的類,并且可以生成相關類對象實例,從而可以調用其方法或則改變某個屬性值。所以JAVA也可以算得上是一個半動態的語言

三、反射機制:


1.反射機制概念
在Java中的反射機制是指在運行狀態中,對于任意一個類都能夠知道這個類所有的屬性和方法;并且對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成為Java語言的反射機制。

2.反射的應用場合
在Java程序中許多對象在運行是都會出現兩種類型:編譯時類型和運行時類型。
編譯時的類型由聲明對象時實用的類型來決定,運行時的類型由實際賦值給對象的類型決定
如:

Person p=new Student();

其中編譯時類型為Person,運行時類型為Student。
除此之外,程序在運行時還可能接收到外部傳入的對象,該對象的編譯時類型為Object,但是程序有需要調用該對象的運行時類型的方法。為了解決這些問題,程序需要在運行時發現對象和類的真實信息。然而,如果編譯時根本無法預知該對象和類屬于哪些類,程序只能依靠運行時信息來發現該對象和類的真實信息,此時就必須使用到反射了。

四、Java反射API


反射API用來生成JVM中的類、接口或則對象的信息。

  • Class類:反射的核心類,可以獲取類的屬性,方法等信息。
  • Field類:Java.lang.reflec包中的類,表示類的成員變量,可以用來獲取和設置類之中的屬性值。
  • Method類: Java.lang.reflec包中的類,表示類的方法,它可以用來獲取類中的方法信息或者執行方法。
  • Constructor類: Java.lang.reflec包中的類,表示類的構造方法。

五、使用反射的步驟


1.步驟

  • 獲取想要操作的類的Class對象
  • 調用Class類中的方法
  • 使用反射API來操作這些信息

2.獲取Class對象的方法

  • 調用某個對象的getClass()方法
Person p=new Person();
Class clazz=p.getClass();
  • 調用某個類的class屬性來獲取該類對應的Class對象
Class clazz=Person.class;
  • 使用Class類中的forName()靜態方法; (最安全/性能最好)
Class clazz=Class.forName("類的全路徑"); (最常用)

3.獲取方法和屬性信息

當我們獲得了想要操作的類的Class對象后,可以通過Class類中的方法獲取并查看該類中的方法和屬性。
示例代碼

<<<<<<<<<<<<<<<<<<<<<<Person類<<<<<<<<<<<<<<<<<<<<<<<<<<
package reflection;

public class Person {
    private String name;
    private String gender;
    private int age;

    public Person() {

    }
    public Person(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    //getter和setter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public String toString(){
        return "姓名:"+name+"  性別:"+gender+"  年齡:"+age;
    }

}

<<<<<<<<<<<<<<<<使用反射<<<<<<<<<<<<<<<<<<<
package reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/*
 * 通過用戶輸入類的全路徑,來獲取該類的成員方法和屬性
 * Declared獲取全部不管是私有和公有
 * 1.獲取訪問類的Class對象
 * 2.調用Class對象的方法返回訪問類的方法和屬性信息
 */
public class Test {

    public static void main(String[] args) {
        try {
            //獲取Person類的Class對象
            Class clazz=Class.forName("reflection.Person");

            //獲取Person類的所有方法信息
            Method[] method=clazz.getDeclaredMethods();
            for(Method m:method){
                System.out.println(m.toString());
            }

            //獲取Person類的所有成員屬性信息
            Field[] field=clazz.getDeclaredFields();
            for(Field f:field){
                System.out.println(f.toString());
            }

            //獲取Person類的所有構造方法信息
            Constructor[] constructor=clazz.getDeclaredConstructors();
            for(Constructor c:constructor){
                System.out.println(c.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

輸出結果:

方法信息:
public java.lang.String reflection.Person.toString()
private java.lang.String reflection.Person.getName()
private void reflection.Person.setName(java.lang.String)
public void reflection.Person.setAge(int)
public int reflection.Person.getAge()
public java.lang.String reflection.Person.getGender()
public void reflection.Person.setGender(java.lang.String)
屬性信息:
private java.lang.String reflection.Person.name
private java.lang.String reflection.Person.gender
private int reflection.Person.age
構造方法信息
private reflection.Person()
public reflection.Person(java.lang.String,java.lang.String,int)

4.創建對象

當我們獲取到所需類的Class對象后,可以用它來創建對象,創建對象的方法有兩種:

  • 使用Class對象的newInstance()方法來創建該Class對象對應類的實例,但是這種方法要求該Class對象對應的類有默認的空構造器。
  • 先使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建 Class對象對應類的實例,通過這種方法可以選定構造方法創建實例。

示例代碼:

package reflection;

import java.lang.reflect.Constructor;
public class Demo01 {

    public static void main(String[] args) {
        try {
            //獲取Person類的Class對象
            Class clazz=Class.forName("reflection.Person"); 
            /**
             * 第一種方法創建對象
             */
            //創建對象
            Person p=(Person) clazz.newInstance();
            //設置屬性
            p.setName("張三");
            p.setAge(16);
            p.setGender("男");
            System.out.println(p.toString());

            /**
             * 第二種方法創建
             */
            //獲取構造方法
            Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
            //創建對象并設置屬性
            Person p1=(Person) c.newInstance("李四","男",20);
            System.out.println(p1.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

輸出結果:

姓名:張三 性別:男 年齡: 16
姓名:李四 性別:男 年齡: 20

應用——Annotation(注解)
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建于使用注解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義注解處理器。


注解處理器類庫(java.lang.reflect.AnnotatedElement):
  Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:

  • Class:類定義 - Constructor:構造器定義
  • Field:類的成員變量定義
  • Method:類的方法定義
  • Package:類的包定義

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義為運行時的Annotation后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的如下四個方法來訪問Annotation信息:

方法1: T getAnnotation(Class annotationClass): 返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class< ?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

一個簡單的注解處理器:


/***********注解聲明***************/

/**
 * 水果名稱注解

 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}

/**
 * 水果顏色注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 顏色枚舉
     */
    public enum Color {
        BULE, RED, GREEN
    };

    /**
     * 顏色屬性
     */
    Color fruitColor() default Color.GREEN;

}

/**
 * 水果供應者注解
 * 
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應商編號
     */
    public int id() default -1;

    /**
     * 供應商名稱
     */
    public String name() default "";

    /**
     * 供應商地址
     */
    public String address() default "";
}

/*********** 注解使用 ***************/

public class Apple {

    @FruitName("Apple")
    private String appleName;

    @FruitColor(fruitColor = Color.RED)
    private String appleColor;

    @FruitProvider(id = 1, name = "陜西紅富士集團", address = "陜西省西安市延安路89號紅富士大廈")
    private String appleProvider;

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleName() {
        return appleName;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleProvider() {
        return appleProvider;
    }

    public void displayName() {
        System.out.println("水果的名字是:蘋果");
    }
}

/*********** 注解處理器 ***************/

public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz) {

        String strFruitName = " 水果名稱:";
        String strFruitColor = " 水果顏色:";
        String strFruitProvicer = "供應商信息:";

        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(FruitName.class)) {
                FruitName fruitName = (FruitName) field
                        .getAnnotation(FruitName.class);
                strFruitName = strFruitName + fruitName.value();
                System.out.println(strFruitName);
            } else if (field.isAnnotationPresent(FruitColor.class)) {
                FruitColor fruitColor = (FruitColor) field
                        .getAnnotation(FruitColor.class);
                strFruitColor = strFruitColor
                        + fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
            } else if (field.isAnnotationPresent(FruitProvider.class)) {
                FruitProvider fruitProvider = (FruitProvider) field
                        .getAnnotation(FruitProvider.class);
                strFruitProvicer = " 供應商編號:" + fruitProvider.id() + " 供應商名稱:"
                        + fruitProvider.name() + " 供應商地址:"
                        + fruitProvider.address();
                System.out.println(strFruitProvicer);
            }
        }
    }
}

/***********main方法***************/
public class FruitRun {

    public static void main(String[] args) {

        FruitInfoUtil.getFruitInfo(Apple.class);

    }

}

/***********輸出結果***************/
 水果名稱:Apple
 水果顏色:RED
 供應商編號:1 供應商名稱:陜西紅富士集團 供應商地址:陜西省西安市延安路89號紅富士大廈

Java注解的基礎知識點(見下面導圖):[圖片上傳失敗...(image-c6dc6d-1531036897498)]

通過反射操作泛型,注解
上篇文章我介紹了Java反射的基本知識,如果沒看過的同學可以去看我的上一篇文章 反射概念與基礎,今天這篇文章主要介紹一下反射地具體應用實例,分別是通過Java反射操作泛型,和反射操作注解(不了解”注解”的同學可以看我的另一篇文章java注解)。

一、反射操作泛型(Generic)


Java采用泛型擦除機制來引入泛型。Java中的泛型僅僅是給編譯器Javac使用的,確保數據的安全性和免去強制類型轉換的麻煩。但是編譯一旦完成,所有和泛型有關的類型全部被擦除。
  為了通過反射操作這些類型以迎合實際開發的需要,Java新增了ParameterizedType,GenericArrayType,TypeVariableWildcardType幾種類型來代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型

  • ParameterizedType:表示一種參數化的類型,比如Collection< String >
  • GenericArrayType:表示一種元素類型是參數化類型或者類型變量的數組類型
  • TypeVariable:是各種類型變量的公共父接口
  • WildcardType:代表一種通配符類型表達式,比如?、? extends Number、? super Integer。(wildcard是一個單詞:就是”通配符“)

代碼示例

package reflection;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * 通過反射獲取泛型信息
 *
 */
public class Demo{  
    //定義兩個帶泛型的方法
    public void test01(Map<String,Person> map,List<Person> list){
        System.out.println("Demo.test01()");
    }   
    public Map<Integer,Person> test02(){
        System.out.println("Demo.test02()");
        return null;
    }   

    public static void main(String[] args) {
        try {           
            //獲得指定方法參數泛型信息
            Method m = Demo.class.getMethod("test01", Map.class,List.class);
            Type[] t = m.getGenericParameterTypes();

            for (Type paramType : t) {
                System.out.println("#"+paramType);
                if(paramType instanceof ParameterizedType){
                    //獲取泛型中的具體信息
                    Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
                    for (Type genericType : genericTypes) {
                        System.out.println("泛型類型:"+genericType);
                    }
                }
            }   

            //獲得指定方法返回值泛型信息
            Method m2 = Demo.class.getMethod("test02", null);
            Type returnType = m2.getGenericReturnType();
            if(returnType instanceof ParameterizedType){
                    Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();

                    for (Type genericType : genericTypes) {
                        System.out.println("返回值,泛型類型:"+genericType);
                    }                   
            }       

        } catch (Exception e) {
            e.printStackTrace();
        }   
    }
}

輸出結果:

java.util.Map< java.lang.String, reflection.Person >

泛型類型:class java.lang.String
泛型類型:class reflection.Person

java.util.List< reflection.Person >

泛型類型:class reflection.Person

返回值,泛型類型:class java.lang.Integer
返回值,泛型類型:class reflection.Person

二、反射操作注解(Annotation)


具體使用可以就看我的之前的文章 注解處理器

好了,介紹了兩個簡單的反射的應用,在順便講一下Java反射機制的性能問題。

三、反射性能測試


Method/Constructor/Field/Element 都繼承了 AccessibleObject , AccessibleObject 類中有一個 setAccessible 方法:


public void setAccessible(booleanflag)throws SecurityException 
{
    ...
}

該方法有兩個作用:

  1. 啟用/禁用訪問安全檢查開關:值為true,則指示反射的對象在使用時取消Java語言訪問檢查; 值為false,則指示應該實施Java語言的訪問檢查;
  2. 可以禁止安全檢查, 提高反射的運行效率.

測試代碼

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestReflect {
    public static void testNoneReflect() {
        Person user = new Person();

        long start = System.currentTimeMillis();
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            user.getName();
        }
        long count = System.currentTimeMillis() - start;
        System.out.println("沒有反射, 共消耗 <" + count + "> 毫秒");
    }

    public static void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Person user = new Person();
        Method method = Class.forName("reflection.Person").getMethod("getName");

        long start = System.currentTimeMillis();
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            method.invoke(user, null);
        }
        long count = System.currentTimeMillis() - start;
        System.out.println("沒有訪問權限, 共消耗 <" + count + "> 毫秒");
    }

    public static void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Person user = new Person();
        Method method = Class.forName("reflection.Person").getMethod("getName");
        method.setAccessible(true);

        long start = System.currentTimeMillis();
        for (long i = 0; i < Integer.MAX_VALUE; ++i) {
            method.invoke(user, null);
        }
        long count = System.currentTimeMillis() - start;
        System.out.println("有訪問權限, 共消耗 <" + count + "> 毫秒");
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        testNoneReflect();
        testNotAccess();
        testUseAccess();
    }
}

輸出結果:
沒有反射, 共消耗 <912> 毫秒
沒有訪問權限, 共消耗 <4366> 毫秒 有訪問權限, 共消耗 <2843> 毫秒

可以看到使用反射會比直接調用慢2000 毫秒 ,但是前提是該方法會執行20E+次(而且服務器的性能也肯定比我的機器要高),因此在我們的實際開發中,其實是不用擔心反射機制帶來的性能消耗的,而且禁用訪問權限檢查,也會有性能的提升。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 1. 前言 ??Java的反射功能平時已使用了多次,但從來沒有仔細的梳理過,趁著最近在梳理Java基礎,再來系統的...
    騎著烏龜去看海閱讀 1,371評論 0 28
  • 詳解Java反射機制(Reflection) 反射機制的作用 JAVA反射機制是在運行狀態中,對于任意一個類,都能...
    顏洛濱閱讀 1,029評論 0 2
  • 聽著《最后的莫西干人》悠遠而神秘、大氣而蒼涼。 有時候很喜歡聽英文歌曲,不是因為英文有多好、而是不想知道歌詞的...
    別樣奇女子閱讀 448評論 0 1
  • 殘陽斜暮煙云淡, 瘦湖直塔秋水寒。 二十四橋枯柳亂, 殘枝敗荷鴛鴦難。 玉欄獨倚望遠山, 佳人南行已無緣。 燕子樓...
    云逸1108閱讀 59評論 0 2