Java基礎系列—Java反射

可以分析類能力的程序稱爲反射(reflective)。反射機制的功能很是強大,主要提供了以下功能:html

  • 對於任意一個類,都可以知道這個類的全部屬性和方法;java

  • 對於任意一個對象,都可以調用它的任意方法和屬性;api

Class類

在程序運行期間,Java運行時系統始終爲全部的對象維護一個被稱爲運行時的類型標識。這個信息跟蹤着每一個對象所屬的類。虛擬機利用運行時類型信息選擇相應的方法執行。然而,能夠經過專門的Java類訪問這些信息,這個類爲java.lang.Class數組

類Class的實例表示正在運行的Java應用程序中的類和接口。 枚舉是一種類,一個註釋是一種接口。每一個數組也屬於一個類,它被反映爲具備相同元素類型和維數的全部數組共享的Class對象。 原始Java類型(boolean,byte,char,short,int,long,float和double)和關鍵字void也表示爲Class對象。安全

Class類沒有公共構造函數。Class對象由Java虛擬機自動構建,由於加載了類,而且經過調用類加載器中的defineClass方法。oracle

雖然咱們不能new一個Class對象,可是卻能夠經過已有的類獲得一個Class對象,共有以下三種方式:ide

一、Class類的forName方法函數

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

二、經過一個類的對象的getClass()方法測試

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

三、Class字面常量this

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反射

相關文章
相關標籤/搜索