Java基礎系列—Java反射

牛覓發表於2018-05-03

能夠分析類能力的程式稱為反射(reflective)。反射機制的功能非常強大,主要提供瞭如下功能:

  • 對於任意一個類,都能夠知道這個類的所有屬性和方法;

  • 對於任意一個物件,都能夠呼叫它的任意方法和屬性;

Class類

在程式執行期間,Java執行時系統始終為所有的物件維護一個被稱為執行時的型別標識。這個資訊跟蹤著每個物件所屬的類。虛擬機器利用執行時型別資訊選擇相應的方法執行。然而,可以通過專門的Java類訪問這些資訊,這個類為java.lang.Class

類Class的例項表示正在執行的Java應用程式中的類和介面。 列舉是一種類,一個註釋是一種介面。每個陣列也屬於一個類,它被反映為具有相同元素型別和維數的所有陣列共享的Class物件。 原始Java型別(boolean,byte,char,short,int,long,float和double)和關鍵字void也表示為Class物件。

Class類沒有公共建構函式。Class物件由Java虛擬機器自動構建,因為載入了類,並且通過呼叫類載入器中的defineClass方法。

雖然我們不能new一個Class物件,但是卻可以通過已有的類得到一個Class物件,共有如下三種方式:

1、Class類的forName方法

 Class<?> clazz = Class.forName("com.codersm.study.jdk.reflect.Person");
複製程式碼

2、通過一個類的物件的getClass()方法

 Class<?> clazz = new Person().getClass();
複製程式碼

3、Class字面常量

 Class<Person> clazz = Person.Class;
複製程式碼

小結:

  1. 注意呼叫forName方法時需要捕獲一個名稱為ClassNotFoundException的異常,因為forName方法在編譯器是無法檢測到其傳遞的字串對應的類是否存在的,只能在程式執行時進行檢查,如果不存在就會丟擲ClassNotFoundException異常。

  2. Class字面常量這種方法更加簡單,更安全。因為它在編譯器就會受到編譯器的檢查同時由於無需呼叫forName方法效率也會更高,因為通過字面量的方法獲取Class物件的引用不會自動初始化該類? 更加有趣的是字面常量的獲取Class物件引用方式不僅可以應用於普通的類,也可以應用用介面,陣列以及基本資料型別。

Class類提供了大量的例項方法來獲取該Class物件所對應的詳細資訊,Class類大致包含如下方法,其中每個方法都包含多個過載版本,因此我們只是做簡單的介紹,詳細請參考JDK文件

  • 獲取類資訊

    獲取內容 方法簽名
    構造器 Constructor<T> getConstructor(Class<?>... parameterTypes)
    方法 Method getMethod(String name, Class<?>... parameterTypes)
    屬性 Field getField(String name)
    Annotation <A extends Annotation> A getAnnotation(Class<A> annotationClass)
    內部類 Class<?>[] getDeclaredClasses()
    外部類 Class<?> getDeclaringClass()
    實現的介面 Class<?>[] getInterfaces()
    修飾符 int getModifiers()
    所在包 Package getPackage()
    類名 String getName()
    簡稱 String getSimpleName()

    注: getDeclaredXxx方法可以獲取所有的Xxx,無論private/public。

  • 判斷類本身資訊的方法

    判斷內容 方法簽名
    註解型別? boolean isAnnotation()
    使用了該Annotation修飾? boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
    匿名類? boolean isAnonymousClass()
    陣列? boolean isArray()
    列舉? boolean isEnum()
    原始型別? boolean isPrimitive()
    介面? boolean isInterface()
    obj是否是該Class的例項 boolean isInstance(Object obj)
  • 使用反射生成並操作對

    • 程式可以通過Method物件來執行相應的方法;

    • 通過Constructor物件來呼叫對應的構造器建立例項;

    • 通過Filed物件直接訪問和修改物件的成員變數值。

建立物件

通過反射來生成物件的方式有兩種:

  1. 使用Class物件的newInstance()方法來建立該Class物件對應類的例項(這種方式要求該Class物件的對應類有預設構造器)

  2. 先使用Class物件獲取指定的Constructor物件, 再呼叫Constructor物件的newInstance()方法來建立該Class物件對應類的例項(通過這種方式可以選擇指定的構造器來建立例項)

class Person {

    private String name;

    private Integer age;

    public Person() {
        this.name = "system";
        this.age = 99;
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

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


public class Test {

    public static void main(String[] args) throws Exception {
        Class<Person> pClass = Person.class;
        // 通過第1種方式建立物件
        Person p = pClass.newInstance();
        System.out.println(p);
        // 通過第2種方式建立物件
        Constructor<Person> constructor = pClass.getDeclaredConstructor(
                                                    String.class, Integer.class);
        Person person2 = constructor.newInstance("zhangsan",20);
        System.out.println(person2);
    }
}
複製程式碼

呼叫方法

當獲取到某個類對應的Class物件之後, 就可以通過該Class物件的getMethod來獲取一個Method陣列或Method物件.每個Method物件對應一個方法,在獲得Method物件之後,就可以通過呼叫invoke方法來呼叫該Method物件對應的方法。

    Person person = new Person();
    // 獲取getAge方法
    Method getAgeMethod = person.getClass().getMethod("getAge",null);
    // 呼叫invoke方法來呼叫getAge方法
    Integer age = (Integer) getAgeMethod.invoke(person,null);
    System.out.println(age);
複製程式碼

訪問成員變數

通過Class物件的的getField()方法可以獲取該類所包含的全部或指定的成員變數Field,Filed提供瞭如下兩組方法來讀取和設定成員變數值.

  1. getXxx(Object obj): 獲取obj物件的該成員變數的值, 此處的Xxx對應8中基本型別,如果該成員變數的型別是引用型別, 則取消get後面的Xxx;

  2. setXxx(Object obj, Xxx val): 將obj物件的該成員變數值設定成val值.此處的Xxx對應8種基本型別, 如果該成員型別是引用型別, 則取消set後面的Xxx;

 Person person = new Person();
 // 獲取name成員變數Field
 Field nameField = person.getClass().getDeclaredField("name");
 // 啟用訪問控制許可權
 nameField.setAccessible(true);
 // 獲取person物件的成員變數name的值
 String name = (String) nameField.get(person);
 System.out.println("name = " + name);
 // 設定person物件的成員變數name的值
 nameField.set(person, "lisi");
 System.out.println(person);
複製程式碼

運算元組

在Java的java.lang.reflect包中存在著一個可以動態運算元組的類,Array提供了動態建立和訪問Java陣列的方法。Array允許在執行get或set操作進行取值和賦值。在Class類中與陣列關聯的方法是:

方法 說明
public native Class<?> getComponentType() 返回表示陣列元素型別的Class,即陣列的型別
public native boolean isArray() 判定此Class物件是否表示一個陣列類

java.lang.reflect.Array中的常用靜態方法如下:

  • newInstance(Class<?> componentType, int length)

  • newInstance(Class<?> componentType, int... dimensions)

  • int getLength(Object array)

  • Object get(Object array, int index)

  • void set(Object array, int index, Object value)

實現通用陣列複製功能,其程式碼如下:

public class GenericityArray {

    public static <T> T[] copy(T[] clazz) {
        return (T[]) Array.newInstance(
                        clazz.getClass().getComponentType(), 
                        clazz.length);
}

    public static void main(String[] args) {
        Integer[] array = {1, 2, 3};
        Integer[] copyArray = GenericityArray.copy(array);
        System.out.println(copyArray.length);
    }
}
複製程式碼

使用反射獲取泛型資訊

為了通過反射操作泛型以迎合實際開發的需要, Java新增了java.lang.reflect.ParameterizedTypejava.lang.reflect.GenericArrayTypejava.lang.reflect.TypeVariablejava.lang.reflect.WildcardType

型別 含義
ParameterizedType 一種引數化型別, 比如Collection
GenericArrayType 一種元素型別是引數化型別或者型別變數的陣列型別
TypeVariable 各種型別變數的公共介面
WildcardType 一種萬用字元型別表示式, 如? extends Number

其中, 我們可以使用ParameterizedType來獲取泛型資訊.

public class Client {
 
    private Map<String, Object> objectMap;
 
    public void test(Map<String, User> map, String string) {
    }
 
    public Map<User, Bean> test() {
        return null;
    }
 
    /**
     * 測試屬性型別
     *
     * @throws NoSuchFieldException
     */
    @Test
    public void testFieldType() throws NoSuchFieldException {
        Field field = Client.class.getDeclaredField("objectMap");
        Type gType = field.getGenericType();
        // 列印type與generic type的區別
        System.out.println(field.getType());
        System.out.println(gType);
        System.out.println("**************");
        if (gType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) gType;
            Type[] types = pType.getActualTypeArguments();
            for (Type type : types) {
                System.out.println(type.toString());
            }
        }
    }
 
    /**
     * 測試引數型別
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testParamType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test", Map.class, String.class);
        Type[] parameterTypes = testMethod.getGenericParameterTypes();
        for (Type type : parameterTypes) {
            System.out.println("type -> " + type);
            if (type instanceof ParameterizedType) {
                Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualType : actualTypes) {
                    System.out.println("\tactual type -> " + actualType);
                }
            }
        }
    }
 
    /**
     * 測試返回值型別
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testReturnType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test");
        Type returnType = testMethod.getGenericReturnType();
        System.out.println("return type -> " + returnType);
 
        if (returnType instanceof ParameterizedType) {
            Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
            for (Type actualType : actualTypes) {
                System.out.println("\tactual type -> " + actualType);
            }
        }
    }
}
複製程式碼

使用反射獲取註解

使用反射獲取註解資訊的相關介紹, 請參看Java註解實踐

參考資料

  1. 深入理解Java型別資訊(Class物件)與反射機制

  2. Java反射

相關文章