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

對于Java反射,平常工作中雖然經(jīng)常用到,但一直以來都沒有系統(tǒng)總結(jié)過,所以趁著目前有空總結(jié)一下,加深一下理解。

如果發(fā)現(xiàn)謬誤,歡迎各位批評指正。

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

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

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.
通過反射,Java代碼可以發(fā)現(xiàn)有關(guān)已加載類的字段,方法和構(gòu)造函數(shù)的信息,并可以在安全限制內(nèi)對這些字段,方法和構(gòu)造函數(shù)進行操作。

簡而言之,你可以在運行狀態(tài)中通過反射機制做到:

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

這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。

在我看來我們平時使用Java反射主要涉及兩個類(接口)Class, Member,如果把這兩個類搞清楚了,反射基本就ok了。

Class

提到反射就不得不提到Class,Class可以說是反射能夠?qū)崿F(xiàn)的基礎(chǔ);注意這里說的Class與class關(guān)鍵字不是同一種東西。class關(guān)鍵字是在聲明java類時使用的;而Class 是java JDK提供的一個類,完整路徑為 java.lang.Class,本質(zhì)上與Math, String 或者你自己定義各種類沒什么區(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.

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

下面就通過一個簡單的例子來說明如何通過反射實例化一個對象。

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對象
        Class c = Animal.class;
        try {
            //通過Class對象反射獲取Animal類的構(gòu)造方法
            Constructor constructor = c.getConstructor(String.class, int.class);
            //調(diào)用構(gòu)造方法獲取Animal實例
            Animal animal = (Animal) constructor.newInstance( "Jack", 3);
            //將構(gòu)造出來的Animal對象打印出來
            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();
        }
    }
}

下面我們來看下打印值

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

可以看出我們確實成功構(gòu)造出了Animal對象,而且在這過程中Class功不可沒。

有人說你這也太費事了,都知道Animal對象了,我分分鐘就能給你new出來了。

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

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

如何獲取Class

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

  • Object.getClass()
    通過對象實例獲取對應(yīng)Class對象,如
//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();

然而對于基本類型無法使用這種方法

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

  • The .class Syntax
    通過類的類型獲取Class對象,基本類型同樣可以使用這種方法,如
//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()
    通過類的全限定名獲取Class對象, 基本類型無法使用此方法
Class c = Class.forName("java.lang.String");

對于數(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;   //等價于 double.class.

Class c = Void.TYPE;

  • Methods that Return Classes
    另外還有一些反射方法可以獲取Class對象,但前提是你已經(jīng)獲取了一個Class對象。
    有點拗口,比如說你已經(jīng)獲取了一個類的Class對象,就可以通過反射方法獲取這個類的父類的Class對象。

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

通過Class獲取類修飾符和類型

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

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

下面我們就以HashMap為例,通過一個Demo來說明如何獲取這些信息

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 --");
        }
        //獲取類實現(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 .

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

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

反射就是通過這三個類才能在運行時改變對象狀態(tài)。下面就讓我們通過一些例子來說明如何通過反射操作它們。

首先建一個測試類

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

通過Field你可以訪問給定對象的類變量,包括獲取變量的類型、修飾符、注解、變量名、變量的值或者重新設(shè)置變量值,即使變量是private的。

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

  • 獲取變量類型、修飾符、注解
    一個例子說明問題

    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è)置變量值
    給定一個對象和它的成員變量名稱,就能通過反射獲取和改變該變量的值。
    什么都不說了,沒有什么是不能通過一個例子解決的, Easy~
    仍然是上面的測試類,通過反射獲取并改變Cat的name和age.
    public void testField(){
        Cat cat = new Cat("Tom", 2);
        Class c = cat.getClass();
        try {
            //注意獲取private變量時,需要用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();
        }
    }

嗯?竟然報錯?

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,說我們沒有權(quán)限操作變量name;回到Cat類中查看一下name變量。

    private String name;

原來name變量是private,Java運行時會進行訪問權(quán)限檢查,private類型的變量無法進行直接訪問,剛剛進行的反射操作并沒有打破這種封裝,所以我們依然沒有權(quán)限對private屬性進行直接訪問。
難道就沒有辦法打破這種限制嗎?必須有!強大的反射早已暗中為我們準(zhǔn)備好了一切。
反射包里為我們提供了一個強大的類

public final class Field extends AccessibleObject implements Member

Field正是AccessibleObject的子類,那么簡單了,只要在訪問私有變量前調(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ù)無法訪問,記得處理方法一樣。

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é)主要介紹如何通過反射訪問對象的方法。

注意:獲取帶參數(shù)方法時,如果參數(shù)類型錯誤會報NoSuchMethodException,對于參數(shù)是泛型的情況,泛型須當(dāng)成Object處理(Object.class)

  • 獲取方法返回類型
    getReturnType() 獲取目標(biāo)方法返回類型對應(yīng)的Class對象
    getGenericReturnType() 獲取目標(biāo)方法返回類型對應(yīng)的Type對象
    這兩個方法有啥區(qū)別呢?
    1. getReturnType()返回類型為Class,getGenericReturnType()返回類型為Type; Class實現(xiàn)Type。
    2. 返回值為普通簡單類型如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>

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

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

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

  • 獲取方法參數(shù)名稱
    .class文件中默認(rèn)不存儲方法參數(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ù)名稱,如果編譯時未加上`-parameters`,返回的名稱形如`argX`, X為參數(shù)在方法聲明中的位置,從0開始
    p.getModifiers(); //獲取參數(shù)修飾符
    p.isNamePresent();  //.class文件中是否保存參數(shù)名稱, 編譯時加上`-parameters`返回true,反之flase
}

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

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

Ps:順便多介紹幾個Method方法

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

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

還是以上面測試類Cat為例

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

 Class<?> c = Cat.class;
 try {
     //構(gòu)造Cat實例
     Constructor constructor = c.getConstructor(String.class, int.class);
     Object cat = constructor.newInstance( "Jack", 3);
     //調(diào)用無參方法
     Method sleep = c.getDeclaredMethod("sleep");
     sleep.invoke(cat);
     //調(diào)用定項參數(shù)方法
     Method eat = c.getDeclaredMethod("eat", String.class);
     eat.invoke(cat, "grass");
     //調(diào)用不定項參數(shù)方法
     //不定項參數(shù)可以當(dāng)成數(shù)組來處理
     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)用的方法本身所拋出的異常在反射中都會以InvocationTargetException拋出。換句話說,反射調(diào)用過程中如果異常InvocationTargetException拋出,說明反射調(diào)用本身是成功的,因為這個異常是目標(biāo)方法本身所拋出的異常。

Constructor

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

構(gòu)造方法的名稱、限定符、參數(shù)、聲明的異常等獲取方法都與Method類似,請參照Method

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

例子在Method一節(jié)已經(jīng)寫過,這里直接截取過來

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

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

數(shù)組和枚舉

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

數(shù)組
  • 數(shù)組類型
    數(shù)組類型:數(shù)組本質(zhì)是一個對象,所以它也有自己的類型。
    例如對于int[] intArray,數(shù)組類型為class [I。數(shù)組類型中的[個數(shù)代表數(shù)組的維度,例如[代表一維數(shù)組,[[代表二維數(shù)組;[后面的字母代表數(shù)組元素類型,I代表int,一般為類型的首字母大寫(long類型例外,為J)。
class [B    //byte類型一維數(shù)組
class [S    //short類型一維數(shù)組
class [I    //int類型一維數(shù)組
class [C    //char類型一維數(shù)組
class [J    //long類型一維數(shù)組,J代表long類型,因為L被引用對象類型占用了
class [F    //float類型一維數(shù)組
class [D    //double類型一維數(shù)組
class [Lcom.dada.Season    //引用類型一維數(shù)組
class [[Ljava.lang.String  //引用類型二維數(shù)組

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

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

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

//獲取數(shù)組的值,array為數(shù)組對象,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);

注意:反射支持對數(shù)據(jù)自動加寬,但不允許數(shù)據(jù)narrowing(變窄?真難翻譯)。意思是對于上述set方法,你可以在int類型數(shù)組中 set short類型數(shù)據(jù),但不可以set long類型數(shù)據(jù),否則會報IllegalArgumentException。

  • 多維數(shù)組
    Java反射沒有提供能夠直接訪問多維數(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òu)造方法,方法等;對于普通類所能使用的反射方法,枚舉都能使用;另外java反射額外提供了幾個方法為枚舉服務(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

反射的缺點

沒有任何一項技術(shù)是十全十美的,Java反射擁有強大功能的同時也帶來了一些副作用。

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

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

作者:ming152
鏈接:http://www.lxweimin.com/p/607ff4e79a13
來源:簡書

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

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