Java反射完全解析(轉(zhuǎn))

對(duì)于Java反射遍膜,平常工作中雖然經(jīng)常用到,但一直以來(lái)都沒(méi)有系統(tǒng)總結(jié)過(guò)瓤湘,所以趁著目前有空總結(jié)一下瓢颅,加深一下理解。

如果發(fā)現(xiàn)謬誤弛说,歡迎各位批評(píng)指正挽懦。

本文相關(guān)知識(shí)點(diǎn)大部分總結(jié)自Oracle官方文檔,對(duì)于英文比較好的朋友木人,建議直接閱讀原文檔信柿。

按例,首先描述定義一下醒第。

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
通過(guò)反射角塑,Java代碼可以發(fā)現(xiàn)有關(guān)已加載類的字段,方法和構(gòu)造函數(shù)的信息淘讥,并可以在安全限制內(nèi)對(duì)這些字段,方法和構(gòu)造函數(shù)進(jìn)行操作堤如。

簡(jiǎn)而言之蒲列,你可以在運(yùn)行狀態(tài)中通過(guò)反射機(jī)制做到:

  • 對(duì)于任意一個(gè)類窒朋,都能夠知道這個(gè)類的所有屬性和方法;
  • 對(duì)于任意一個(gè)對(duì)象蝗岖,都能夠調(diào)用它的任意一個(gè)方法和屬性;

這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語(yǔ)言的反射機(jī)制侥猩。

在我看來(lái)我們平時(shí)使用Java反射主要涉及兩個(gè)類(接口)ClassMember抵赢,如果把這兩個(gè)類搞清楚了欺劳,反射基本就ok了。

Class

提到反射就不得不提到Class铅鲤,Class可以說(shuō)是反射能夠?qū)崿F(xiàn)的基礎(chǔ)划提;注意這里說(shuō)的Class與class關(guān)鍵字不是同一種東西。class關(guān)鍵字是在聲明java類時(shí)使用的邢享;而Class 是java JDK提供的一個(gè)類,完整路徑為 java.lang.Class鹏往,本質(zhì)上與Math, String 或者你自己定義各種類沒(méi)什么區(qū)別。

public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
   ...
}

那Class到底在反射中起到什么作用呢骇塘?

For every type of object, the Java virtual machine instantiates an immutable instance of java.lang.Class which provides methods to examine the runtime properties of the object including its members and type information. Class also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs.

對(duì)于每一種類伊履,Java虛擬機(jī)都會(huì)初始化出一個(gè)Class類型的實(shí)例,每當(dāng)我們編寫(xiě)并且編譯一個(gè)新創(chuàng)建的類就會(huì)產(chǎn)生一個(gè)對(duì)應(yīng)Class對(duì)象款违,并且這個(gè)Class對(duì)象會(huì)被保存在同名.class文件里唐瀑。當(dāng)我們new一個(gè)新對(duì)象或者引用靜態(tài)成員變量時(shí),Java虛擬機(jī)(JVM)中的類加載器系統(tǒng)會(huì)將對(duì)應(yīng)Class對(duì)象加載到JVM中插爹,然后JVM再根據(jù)這個(gè)類型信息相關(guān)的Class對(duì)象創(chuàng)建我們需要實(shí)例對(duì)象或者提供靜態(tài)變量的引用值哄辣。
比如創(chuàng)建編譯一個(gè)Shapes類,那么递惋,JVM就會(huì)創(chuàng)建一個(gè)Shapes對(duì)應(yīng)Class類的Class實(shí)例柔滔,該Class實(shí)例保存了Shapes類相關(guān)的類型信息,包括屬性萍虽,方法睛廊,構(gòu)造方法等等,通過(guò)這個(gè)Class實(shí)例可以在運(yùn)行時(shí)訪問(wèn)Shapes對(duì)象的屬性和方法等杉编。另外通過(guò)Class類還可以創(chuàng)建出一個(gè)新的Shapes對(duì)象超全。這就是反射能夠?qū)崿F(xiàn)的原因,可以說(shuō)Class是反射操作的基礎(chǔ)邓馒。
需要特別注意的是嘶朱,每個(gè)class(注意class是小寫(xiě),代表普通類)類光酣,無(wú)論創(chuàng)建多少個(gè)實(shí)例對(duì)象疏遏,在JVM中都對(duì)應(yīng)同一個(gè)Class對(duì)象。

下面就通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明如何通過(guò)反射實(shí)例化一個(gè)對(duì)象。

public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Animal : name = " + name + " age = " + age;
    }
}

 public class TestReflection{
    private static final String TAG = "Reflection";
    public void testReflection(){
        //獲取Animal類的Class對(duì)象
        Class c = Animal.class;
        try {
            //通過(guò)Class對(duì)象反射獲取Animal類的構(gòu)造方法
            Constructor constructor = c.getConstructor(String.class, int.class);
            //調(diào)用構(gòu)造方法獲取Animal實(shí)例
            Animal animal = (Animal) constructor.newInstance( "Jack", 3);
            //將構(gòu)造出來(lái)的Animal對(duì)象打印出來(lái)
            Log.d(TAG, animal.toString());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

下面我們來(lái)看下打印值

03-28 20:12:00.958 2835-2835/? D/Reflection: Animal : name = Jack age = 3

可以看出我們確實(shí)成功構(gòu)造出了Animal對(duì)象财异,而且在這過(guò)程中Class功不可沒(méi)倘零。

有人說(shuō)你這也太費(fèi)事了,都知道Animal對(duì)象了戳寸,我分分鐘就能給你new出來(lái)了呈驶。

Animal animal = new Animal("Jack", 3);

沒(méi)錯(cuò)!
但是如果并不能直接導(dǎo)入Animal類呢疫鹊,如果構(gòu)造方法都是private的呢袖瞻?這個(gè)時(shí)候反射就能大展身手了。

如何獲取Class

說(shuō)Class是反射能夠?qū)崿F(xiàn)的基礎(chǔ)的另一個(gè)原因是:Java反射包java.lang.reflect中的所有類都沒(méi)有public構(gòu)造方法拆吆,要想獲得這些類實(shí)例聋迎,只能通過(guò)Class類獲取。所以說(shuō)如果想使用反射锈拨,必須得獲得Class對(duì)象砌庄。
下面列舉了幾種能夠獲取Class對(duì)象的方法。

  • Object.getClass()
    通過(guò)對(duì)象實(shí)例獲取對(duì)應(yīng)Class對(duì)象奕枢,如
//Returns the Class for String
Class c = "foo".getClass();

enum E { A, B }
//Returns the Class corresponding to the enumeration type E.
Class c = A.getClass();

byte[] bytes = new byte[1024];
//Returns the Class corresponding to an array with component type byte.
Class c = bytes.getClass();

Set<String> s = new HashSet<String>();
//Returns the Class corresponding to java.util.HashSet.
Class c = s.getClass();

然而對(duì)于基本類型無(wú)法使用這種方法

boolean b;
Class c = b.getClass();   // compile-time error

  • The .class Syntax
    通過(guò)類的類型獲取Class對(duì)象,基本類型同樣可以使用這種方法娄昆,如
//The `.class` syntax returns the Class corresponding to the type `boolean`.
Class c = boolean.class;  

//Returns the Class for String
Class c = String.class;

  • Class.forName()
    通過(guò)類的全限定名獲取Class對(duì)象, 基本類型無(wú)法使用此方法
Class c = Class.forName("java.lang.String");

對(duì)于數(shù)組比較特殊

Class cDoubleArray = Class.forName("[D");    //相當(dāng)于double[].class

Class cStringArray = Class.forName("[[Ljava.lang.String;");   //相當(dāng)于String[][].class

  • TYPE Field for Primitive Type Wrappers
    基本類型和void 類型的包裝類可以使用TYPE字段獲取
Class c = Double.TYPE;   //等價(jià)于 double.class.

Class c = Void.TYPE;

  • Methods that Return Classes
    另外還有一些反射方法可以獲取Class對(duì)象缝彬,但前提是你已經(jīng)獲取了一個(gè)Class對(duì)象萌焰。
    有點(diǎn)拗口,比如說(shuō)你已經(jīng)獲取了一個(gè)類的Class對(duì)象谷浅,就可以通過(guò)反射方法獲取這個(gè)類的父類的Class對(duì)象扒俯。

Class.getSuperclass()
獲得給定類的父類Class

// javax.swing.JButton的父類是javax.swing.AbstractButton
Class c = javax.swing.JButton.class.getSuperclass();

類似方法還有:
Class.getClasses()
Class.getDeclaredClasses()
Class.getDeclaringClass()
Class.getEnclosingClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()

通過(guò)Class獲取類修飾符和類型

我們知道類的聲明一般如下表示

[圖片上傳失敗...(image-7d6d12-1528676523570)]

下面我們就以HashMap為例,通過(guò)一個(gè)Demo來(lái)說(shuō)明如何獲取這些信息

public class TestReflection {
    private static final String TAG = "Reflection";
    public void testReflection() {
        Class<?> c = HashMap.class;
        //獲取類名
        Log.d(TAG, "Class : " + c.getCanonicalName());
        //獲取類限定符
        Log.d(TAG, "Modifiers : " + Modifier.toString(c.getModifiers()));
        //獲取類泛型信息
        TypeVariable[] tv = c.getTypeParameters();
        if (tv.length != 0) {
            StringBuilder parameter = new StringBuilder("Parameters : ");
            for (TypeVariable t : tv) {
                parameter.append(t.getName());
                parameter.append(" ");
            }
            Log.d(TAG, parameter.toString());
        } else {
            Log.d(TAG, "  -- No Type Parameters --");
        }
        //獲取類實(shí)現(xiàn)的所有接口
        Type[] intfs = c.getGenericInterfaces();
        if (intfs.length != 0) {
            StringBuilder interfaces = new StringBuilder("Implemented Interfaces : ");
            for (Type intf : intfs){
                interfaces.append(intf.toString());
                interfaces.append(" ");
            }
            Log.d(TAG, interfaces.toString());
        } else {
            Log.d(TAG, "  -- No Implemented Interfaces --");
        }
        //獲取類繼承數(shù)上的所有父類
        List<Class> l = new ArrayList<>();
        printAncestor(c, l);
        if (l.size() != 0) {
            StringBuilder inheritance = new StringBuilder("Inheritance Path : ");
            for (Class<?> cl : l){
                inheritance.append(cl.getCanonicalName());
                inheritance.append(" ");
            }
            Log.d(TAG, inheritance.toString());
        } else {
            Log.d(TAG, "  -- No Super Classes --%n%n");
        }
        //獲取類的注解(只能獲取到 RUNTIME 類型的注解)
        Annotation[] ann = c.getAnnotations();
        if (ann.length != 0) {
            StringBuilder annotation = new StringBuilder("Annotations : ");
            for (Annotation a : ann){
                annotation.append(a.toString());
                annotation.append(" ");
            }
            Log.d(TAG, annotation.toString());
        } else {
            Log.d(TAG, "  -- No Annotations --%n%n");
        }
    }
    private static void printAncestor(Class<?> c, List<Class> l) {
        Class<?> ancestor = c.getSuperclass();
        if (ancestor != null) {
            l.add(ancestor);
            printAncestor(ancestor, l);
        }
    }
}

打印結(jié)果如下

03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Class : java.util.HashMap
03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Modifiers : public
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Parameters : K  V  
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Implemented Interfaces : java.util.Map<K, V>  interface java.lang.Cloneable  interface java.io.Serializable  
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Inheritance Path : java.util.AbstractMap  java.lang.Object  
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection:   -- No Annotations --

Member

Reflection defines an interface java.lang.reflect.Member which is implemented by java.lang.reflect.Field, java.lang.reflect.Method, and java.lang.reflect.Constructor .

對(duì)于Member接口可能會(huì)有人不清楚是干什么的一疯,但如果提到實(shí)現(xiàn)它的三個(gè)實(shí)現(xiàn)類撼玄,估計(jì)用過(guò)反射的人都能知道。我們知道類成員主要包括構(gòu)造函數(shù)墩邀,變量方法掌猛,Java中的操作基本都和這三者相關(guān),而Member的這三個(gè)實(shí)現(xiàn)類就分別對(duì)應(yīng)他們眉睹。

java.lang.reflect.Field :對(duì)應(yīng)類變量
java.lang.reflect.Method :對(duì)應(yīng)類方法
java.lang.reflect.Constructor :對(duì)應(yīng)類構(gòu)造函數(shù)

反射就是通過(guò)這三個(gè)類才能在運(yùn)行時(shí)改變對(duì)象狀態(tài)荔茬。下面就讓我們通過(guò)一些例子來(lái)說(shuō)明如何通過(guò)反射操作它們。

首先建一個(gè)測(cè)試類

public class Cat {
    public static final String TAG = Cat.class.getSimpleName();
    private String name;
    @Deprecated
    public int age;

    public Cat(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return name;
    }

    public void eat(String food){
        Log.d(TAG, "eat food " + food);
    }

    public void eat(String... foods){
        StringBuilder s = new StringBuilder();
        for(String food : foods){
            s.append(food);
            s.append(" ");
        }
        Log.d(TAG, "eat food " + s.toString());
    }

    public void sleep(){
        Log.d(TAG, "sleep");
    }

    @Override
    public String toString() {
        return "name = " + name + " age = " + age;
    }
}

Field

通過(guò)Field你可以訪問(wèn)給定對(duì)象的類變量竹海,包括獲取變量的類型慕蔚、修飾符、注解斋配、變量名孔飒、變量的值或者重新設(shè)置變量值灌闺,即使變量是private的。

  • 獲取Field
    Class提供了4種方法獲得給定類的Field十偶。
    getDeclaredField(String name)
    獲取指定的變量(只要是聲明的變量都能獲得菩鲜,包括private)
    getField(String name)
    獲取指定的變量(只能獲得public的)
    getDeclaredFields()
    獲取所有聲明的變量(包括private)
    getFields()
    獲取所有的public變量

  • 獲取變量類型、修飾符惦积、注解
    一個(gè)例子說(shuō)明問(wèn)題

    public void testField(){
        Class c = Cat.class;
        Field[] fields = c.getDeclaredFields();
        for(Field f : fields){
            StringBuilder builder = new StringBuilder();
            //獲取名稱
            builder.append("filed name = ");
            builder.append(f.getName());
            //獲取類型
            builder.append(" type = ");
            builder.append(f.getType());
            //獲取修飾符
            builder.append(" modifiers = ");
            builder.append(Modifier.toString(f.getModifiers()));
            //獲取注解
            Annotation[] ann = f.getAnnotations();
            if (ann.length != 0) {
                builder.append(" annotations = ");
                for (Annotation a : ann){
                    builder.append(a.toString());
                    builder.append(" ");
                }
            } else {
                builder.append("  -- No Annotations --");
            }
            Log.d(TAG, builder.toString());
        }
    }

打印結(jié)果:

filed name = age type = int modifiers = public annotations = @java.lang.Deprecated() 
filed name = name type = class java.lang.String modifiers = private  -- No Annotations --
filed name = TAG type = class java.lang.String modifiers = public static final  -- No Annotations --

  • 獲取、設(shè)置變量值
    給定一個(gè)對(duì)象和它的成員變量名稱猛频,就能通過(guò)反射獲取和改變?cè)撟兞康闹怠?br> 什么都不說(shuō)了狮崩,沒(méi)有什么是不能通過(guò)一個(gè)例子解決的, Easy~
    仍然是上面的測(cè)試類鹿寻,通過(guò)反射獲取并改變Cat的name和age.
    public void testField(){
        Cat cat = new Cat("Tom", 2);
        Class c = cat.getClass();
        try {
            //注意獲取private變量時(shí)睦柴,需要用getDeclaredField
            Field fieldName = c.getDeclaredField("name");
            Field fieldAge = c.getField("age");
            //反射獲取名字, 年齡
            String name = (String) fieldName.get(cat);
            int age = fieldAge.getInt(cat);
            Log.d(TAG, "before set, Cat name = " + name + " age = " + age);
            //反射重新set名字和年齡
            fieldName.set(cat, "Timmy");
            fieldAge.setInt(cat, 3);
            Log.d(TAG, "after set, Cat " + cat.toString());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

嗯?竟然報(bào)錯(cuò)毡熏?

System.err: java.lang.IllegalAccessException: Class java.lang.Class<com.example.ming.testnestscrollview.TestReflection> cannot access private  field java.lang.String com.example.ming.testnestscrollview.Cat.name of class java.lang.Class<com.example.ming.testnestscrollview.Cat>
System.err:     at java.lang.reflect.Field.get(Native Method)
System.err:     at com.example.ming.testnestscrollview.TestReflection.testField(TestReflection.java:22)
System.err:     at com.example.ming.testnestscrollview.MainActivity.onCreate(MainActivity.java:17)

觀察一下異常信息java.lang.IllegalAccessException坦敌,說(shuō)我們沒(méi)有權(quán)限操作變量name;回到Cat類中查看一下name變量痢法。

    private String name;

原來(lái)name變量是private狱窘,Java運(yùn)行時(shí)會(huì)進(jìn)行訪問(wèn)權(quán)限檢查,private類型的變量無(wú)法進(jìn)行直接訪問(wèn)财搁,剛剛進(jìn)行的反射操作并沒(méi)有打破這種封裝蘸炸,所以我們依然沒(méi)有權(quán)限對(duì)private屬性進(jìn)行直接訪問(wèn)。
難道就沒(méi)有辦法打破這種限制嗎尖奔?必須有搭儒!強(qiáng)大的反射早已暗中為我們準(zhǔn)備好了一切。
反射包里為我們提供了一個(gè)強(qiáng)大的類

public final class Field extends AccessibleObject implements Member

Field正是AccessibleObject的子類茴扁,那么簡(jiǎn)單了铃岔,只要在訪問(wèn)私有變量前調(diào)用filed.setAccessible(true)就可以了

            ...
            fieldName.setAccessible(true);
            //反射獲取名字, 年齡
            String name = (String) fieldName.get(cat);
            ...

打印結(jié)果

TestReflection: before set, Cat name = Tom age = 2
TestReflection: after set, Cat name = Timmy age = 3

Bingo!

注意MethodConstructor也都是繼承AccessibleObject,所以如果遇到私有方法和私有構(gòu)造函數(shù)無(wú)法訪問(wèn)丹弱,記得處理方法一樣德撬。

Method

The java.lang.reflect.Method class provides APIs to access information about a method's modifiers, return type, parameters, annotations, and thrown exceptions. It also be used to invoke methods.

這節(jié)主要介紹如何通過(guò)反射訪問(wèn)對(duì)象的方法。

注意:獲取帶參數(shù)方法時(shí)隆檀,如果參數(shù)類型錯(cuò)誤會(huì)報(bào)NoSuchMethodException,對(duì)于參數(shù)是泛型的情況,泛型須當(dāng)成Object處理(Object.class)

  • 獲取方法返回類型
    getReturnType() 獲取目標(biāo)方法返回類型對(duì)應(yīng)的Class對(duì)象
    getGenericReturnType() 獲取目標(biāo)方法返回類型對(duì)應(yīng)的Type對(duì)象
    這兩個(gè)方法有啥區(qū)別呢恐仑?
    1. getReturnType()返回類型為Class泉坐,getGenericReturnType()返回類型為T(mén)ype; Class實(shí)現(xiàn)Type。
    2. 返回值為普通簡(jiǎn)單類型如Object, int, String等裳仆,getGenericReturnType()返回值和getReturnType()一樣
      例如 public String function1()
      那么各自返回值為:
      getReturnType() : class java.lang.String
      getGenericReturnType() : class java.lang.String
    3. 返回值為泛型
      例如public T function2()
      那么各自返回值為:
      getReturnType() : class java.lang.Object
      getGenericReturnType() : T
    4. 返回值為參數(shù)化類型
      例如public Class<String> function3()
      那么各自返回值為:
      getReturnType() : class java.lang.Class
      getGenericReturnType() : java.lang.Class<java.lang.String>

其實(shí)反射中所有形如getGenericXXX()的方法規(guī)則都與上面所述類似腕让。

  • 獲取方法參數(shù)類型
    getParameterTypes() 獲取目標(biāo)方法各參數(shù)類型對(duì)應(yīng)的Class對(duì)象
    getGenericParameterTypes() 獲取目標(biāo)方法各參數(shù)類型對(duì)應(yīng)的Type對(duì)象
    返回值為數(shù)組,它倆區(qū)別同上 “方法返回類型的區(qū)別” 歧斟。

  • 獲取方法聲明拋出的異常的類型
    getExceptionTypes() 獲取目標(biāo)方法拋出的異常類型對(duì)應(yīng)的Class對(duì)象
    getGenericExceptionTypes() 獲取目標(biāo)方法拋出的異常類型對(duì)應(yīng)的Type對(duì)象
    返回值為數(shù)組纯丸,區(qū)別同上

  • 獲取方法參數(shù)名稱
    .class文件中默認(rèn)不存儲(chǔ)方法參數(shù)名稱,如果想要獲取方法參數(shù)名稱静袖,需要在編譯的時(shí)候加上-parameters參數(shù)觉鼻。(構(gòu)造方法的參數(shù)獲取方法同樣)

//這里的m可以是普通方法Method,也可以是構(gòu)造方法Constructor
//獲取方法所有參數(shù)
Parameter[] params = m.getParameters();
for (int i = 0; i < params.length; i++) {
    Parameter p = params[i];
    p.getType();   //獲取參數(shù)類型
    p.getName();  //獲取參數(shù)名稱队橙,如果編譯時(shí)未加上`-parameters`坠陈,返回的名稱形如`argX`, X為參數(shù)在方法聲明中的位置,從0開(kāi)始
    p.getModifiers(); //獲取參數(shù)修飾符
    p.isNamePresent();  //.class文件中是否保存參數(shù)名稱, 編譯時(shí)加上`-parameters`返回true,反之flase
}

獲取方法參數(shù)名稱的詳細(xì)信息請(qǐng)參考o(jì)racle的官方例子MethodParameterSpy

  • 獲取方法修飾符
    方法與Filed等類似
method.getModifiers();

Ps:順便多介紹幾個(gè)Method方法

  1. method.isVarArgs() //判斷方法參數(shù)是否是可變參數(shù)
public Constructor<T> getConstructor(Class<?>... parameterTypes)  //返回true
public Constructor<T> getConstructor(Class<?> [] parameterTypes)  //返回flase

  1. method.isSynthetic() //判斷是否是復(fù)合方法捐康,個(gè)人理解復(fù)合方法是編譯期間編譯器生成的方法仇矾,并不是源代碼中有的方法
  2. method.isBridge() //判斷是否是橋接方法,橋接方法是 JDK 1.5 引入泛型后吹由,為了使Java的泛型方法生成的字節(jié)碼和 1.5 版本前的字節(jié)碼相兼容若未,由編譯器自動(dòng)生成的方法∏泠辏可以參考https://www.cnblogs.com/zsg88/p/7588929.html
  • 通過(guò)反射調(diào)用方法
    反射通過(guò)Method的invoke()方法來(lái)調(diào)用目標(biāo)方法粗合。第一個(gè)參數(shù)為需要調(diào)用的目標(biāo)類對(duì)象,如果方法為static的乌昔,則該參數(shù)為null隙疚。后面的參數(shù)都為目標(biāo)方法的參數(shù)值,順序與目標(biāo)方法聲明中的參數(shù)順序一致磕道。
public native Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

還是以上面測(cè)試類Cat為例

注意:如果方法是private的供屉,可以使用method.setAccessible(true)方法繞過(guò)權(quán)限檢查

 Class<?> c = Cat.class;
 try {
     //構(gòu)造Cat實(shí)例
     Constructor constructor = c.getConstructor(String.class, int.class);
     Object cat = constructor.newInstance( "Jack", 3);
     //調(diào)用無(wú)參方法
     Method sleep = c.getDeclaredMethod("sleep");
     sleep.invoke(cat);
     //調(diào)用定項(xiàng)參數(shù)方法
     Method eat = c.getDeclaredMethod("eat", String.class);
     eat.invoke(cat, "grass");
     //調(diào)用不定項(xiàng)參數(shù)方法
     //不定項(xiàng)參數(shù)可以當(dāng)成數(shù)組來(lái)處理
     Class[] argTypes = new Class[] { String[].class };
     Method varargsEat = c.getDeclaredMethod("eat", argTypes);
     String[] foods = new String[]{
          "grass", "meat"
     };
     varargsEat.invoke(cat, (Object)foods);
  } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
     e.printStackTrace();
 }

被調(diào)用的方法本身所拋出的異常在反射中都會(huì)以InvocationTargetException拋出。換句話說(shuō)溺蕉,反射調(diào)用過(guò)程中如果異常InvocationTargetException拋出伶丐,說(shuō)明反射調(diào)用本身是成功的,因?yàn)檫@個(gè)異常是目標(biāo)方法本身所拋出的異常疯特。

Constructor

這節(jié)主要介紹如何通過(guò)反射訪問(wèn)構(gòu)造方法并通過(guò)構(gòu)造方法構(gòu)建新的對(duì)象哗魂。

構(gòu)造方法的名稱朽色、限定符、參數(shù)组题、聲明的異常等獲取方法都與Method類似葫男,請(qǐng)參照Method

  1. Class.newInstance()僅可用來(lái)調(diào)用無(wú)參的構(gòu)造方法;Constructor.newInstance()可以調(diào)用任意參數(shù)的構(gòu)造方法赵讯。
  2. Class.newInstance()會(huì)將構(gòu)造方法中拋出的異常不作處理原樣拋出;Constructor.newInstance()會(huì)將構(gòu)造方法中拋出的異常都包裝成InvocationTargetException拋出利职。
  3. Class.newInstance()需要擁有構(gòu)造方法的訪問(wèn)權(quán)限;Constructor.newInstance()可以通過(guò)setAccessible(true)方法繞過(guò)訪問(wèn)權(quán)限訪問(wèn)private構(gòu)造方法。

例子在Method一節(jié)已經(jīng)寫(xiě)過(guò),這里直接截取過(guò)來(lái)

Class<?> c = Cat.class;
try {
    Constructor constructor = c.getConstructor(String.class, int.class);
    Cat cat = (Cat) constructor.newInstance( "Jack", 3);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
    e.printStackTrace();
}

注意:反射不支持自動(dòng)封箱,傳入?yún)?shù)時(shí)要小心(自動(dòng)封箱是在編譯期間的叠聋,而反射在運(yùn)行期間)

數(shù)組和枚舉

數(shù)組和枚舉也是對(duì)象柒爸,但是在反射中,對(duì)數(shù)組和枚舉的創(chuàng)建西傀、訪問(wèn)和普通對(duì)象有那么一丟丟的不同斤寇,所以Java反射為數(shù)組和枚舉提供了一些特定的API接口。

數(shù)組
  • 數(shù)組類型
    數(shù)組類型:數(shù)組本質(zhì)是一個(gè)對(duì)象拥褂,所以它也有自己的類型娘锁。
    例如對(duì)于int[] intArray,數(shù)組類型為class [I饺鹃。數(shù)組類型中的[個(gè)數(shù)代表數(shù)組的維度莫秆,例如[代表一維數(shù)組,[[代表二維數(shù)組悔详;[后面的字母代表數(shù)組元素類型镊屎,I代表int,一般為類型的首字母大寫(xiě)(long類型例外茄螃,為J)缝驳。
class [B    //byte類型一維數(shù)組
class [S    //short類型一維數(shù)組
class [I    //int類型一維數(shù)組
class [C    //char類型一維數(shù)組
class [J    //long類型一維數(shù)組,J代表long類型归苍,因?yàn)長(zhǎng)被引用對(duì)象類型占用了
class [F    //float類型一維數(shù)組
class [D    //double類型一維數(shù)組
class [Lcom.dada.Season    //引用類型一維數(shù)組
class [[Ljava.lang.String  //引用類型二維數(shù)組

//獲取一個(gè)變量的類型
Class<?> c = field.getType();
//判斷該變量是否為數(shù)組
if (c.isArray()) {
   //獲取數(shù)組的元素類型
   c.getComponentType()
}

  • 創(chuàng)建和初始化數(shù)組
    Java反射為我們提供了java.lang.reflect.Array類用來(lái)創(chuàng)建和初始化數(shù)組用狱。
//創(chuàng)建數(shù)組, 參數(shù)componentType為數(shù)組元素的類型拼弃,后面不定項(xiàng)參數(shù)的個(gè)數(shù)代表數(shù)組的維度夏伊,參數(shù)值為數(shù)組長(zhǎng)度
Array.newInstance(Class<?> componentType, int... dimensions)

//設(shè)置數(shù)組值,array為數(shù)組對(duì)象肴敛,index為數(shù)組的下標(biāo)署海,value為需要設(shè)置的值
Array.set(Object array, int index, int value)

//獲取數(shù)組的值吗购,array為數(shù)組對(duì)象,index為數(shù)組的下標(biāo)
Array.get(Object array, int index)

例子,用反射創(chuàng)建 int[] array = new int[]{1, 2}

Object array = Array.newInstance(int.class, 2);
Array.setInt(array , 0, 1);
Array.setInt(array , 1, 2);

注意:反射支持對(duì)數(shù)據(jù)自動(dòng)加寬砸狞,但不允許數(shù)據(jù)narrowing(變窄?真難翻譯)捻勉。意思是對(duì)于上述set方法,你可以在int類型數(shù)組中 set short類型數(shù)據(jù)刀森,但不可以set long類型數(shù)據(jù)踱启,否則會(huì)報(bào)IllegalArgumentException。

  • 多維數(shù)組
    Java反射沒(méi)有提供能夠直接訪問(wèn)多維數(shù)組元素的API研底,但你可以把多維數(shù)組當(dāng)成數(shù)組的數(shù)組處理埠偿。
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);

Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);

或者

Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);

Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);

Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);

枚舉

枚舉隱式繼承自java.lang.Enum,Enum繼承自O(shè)bject榜晦,所以枚舉本質(zhì)也是一個(gè)類冠蒋,也可以有成員變量,構(gòu)造方法乾胶,方法等抖剿;對(duì)于普通類所能使用的反射方法,枚舉都能使用识窿;另外java反射額外提供了幾個(gè)方法為枚舉服務(wù)斩郎。
Class.isEnum()
Indicates whether this class represents an enum type
Class.getEnumConstants()
Retrieves the list of enum constants defined by the enum in the order they're declared
java.lang.reflect.Field.isEnumConstant()
Indicates whether this field represents an element of an enumerated type

反射的缺點(diǎn)

沒(méi)有任何一項(xiàng)技術(shù)是十全十美的,Java反射擁有強(qiáng)大功能的同時(shí)也帶來(lái)了一些副作用喻频。

  • 性能開(kāi)銷
    反射涉及類型動(dòng)態(tài)解析缩宜,所以JVM無(wú)法對(duì)這些代碼進(jìn)行優(yōu)化。因此甥温,反射操作的效率要比那些非反射操作低得多锻煌。我們應(yīng)該避免在經(jīng)常被執(zhí)行的代碼或?qū)π阅芤蠛芨叩某绦蛑惺褂梅瓷洹?/li>
  • 安全限制
    使用反射技術(shù)要求程序必須在一個(gè)沒(méi)有安全限制的環(huán)境中運(yùn)行。如果一個(gè)程序必須在有安全限制的環(huán)境中運(yùn)行窿侈,如Applet炼幔,那么這就是個(gè)問(wèn)題了。
  • 內(nèi)部曝光
    由于反射允許代碼執(zhí)行一些在正常情況下不被允許的操作(比如訪問(wèn)私有的屬性和方法)史简,所以使用反射可能會(huì)導(dǎo)致意料之外的副作用--代碼有功能上的錯(cuò)誤乃秀,降低可移植性。反射代碼破壞了抽象性圆兵,因此當(dāng)平臺(tái)發(fā)生改變的時(shí)候跺讯,代碼的行為就有可能也隨著變化。

使用反射的一個(gè)原則:如果使用常規(guī)方法能夠?qū)崿F(xiàn)殉农,那么就不要用反射刀脏。

作者:ming152
鏈接:http://www.reibang.com/p/607ff4e79a13
來(lái)源:簡(jiǎn)書(shū)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市超凳,隨后出現(xiàn)的幾起案子愈污,更是在濱河造成了極大的恐慌耀态,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暂雹,死亡現(xiàn)場(chǎng)離奇詭異首装,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)杭跪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)仙逻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人涧尿,你說(shuō)我怎么就攤上這事系奉。” “怎么了姑廉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵缺亮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我桥言,道長(zhǎng)瞬内,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任限书,我火速辦了婚禮,結(jié)果婚禮上章咧,老公的妹妹穿的比我還像新娘倦西。我一直安慰自己,他們只是感情好赁严,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布扰柠。 她就那樣靜靜地躺著,像睡著了一般疼约。 火紅的嫁衣襯著肌膚如雪卤档。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,713評(píng)論 1 312
  • 那天程剥,我揣著相機(jī)與錄音劝枣,去河邊找鬼。 笑死织鲸,一個(gè)胖子當(dāng)著我的面吹牛舔腾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搂擦,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼稳诚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瀑踢?” 一聲冷哼從身側(cè)響起扳还,我...
    開(kāi)封第一講書(shū)人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤才避,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后氨距,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體桑逝,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年衔蹲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肢娘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舆驶,死狀恐怖橱健,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沙廉,我是刑警寧澤拘荡,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站撬陵,受9級(jí)特大地震影響珊皿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巨税,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一蟋定、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧草添,春花似錦驶兜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至驰后,卻和暖如春肆资,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灶芝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工郑原, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夜涕。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓颤专,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钠乏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栖秕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

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