Java 反射簡介

程式設計師自由之路發表於2020-06-28

本文部分內容參考部落格。點選連結可以檢視原文。


1. 反射的概念

反射是指在執行時將類的屬性、建構函式和方法等元素動態地對映成一個個物件。通過這些物件我們可以動態地生成物件例項,呼叫類的方法和更改類的屬性值。

2. 使用場景

什麼情況下運用JAVA反射呢?如果編譯時根本無法預知物件和類可能屬於哪些類,程式只依靠執行時資訊來發現該物件和類的真實資訊,此時就必須使用反射。

使用反射可以實現下面的功能:

  • 在執行時判斷任意一個物件所屬的類
  • 在執行時構造任意一個類的物件
  • 在執行時判斷任意一個類所具有的方法和屬性
  • 在執行時呼叫任意一個物件的方法
  • 生成動態代理

3. 獲得Class物件的幾種方式

前面已經介紹過了,每個類被載入之後,系統就會為該類生成一個對應的Class物件,通過該Class物件就可以訪問到JVM中的這個類。在Java程式中獲得Class物件通常有如下3種方式。

  • 使用Class類的forName(String clazzName)靜態方法。該方法需要傳入字串引數,該字串引數的值是某個類的全限定類名(必須新增完整包名)。
  • 呼叫某個類的class屬性來獲取該類對應的Class物件。例如,Person.class將會返回Person類對應的Class物件。
  • 呼叫某個物件的getClass()方法。該方法是java.lang.Object類中的一個方法,所以所有的Java物件都可以呼叫該方法,該方法將會返回該物件所屬類對應的Class物件。

對於第一種方式和第二種方式都是直接根據類來取得該類的Class物件,相比之下,第二種方式有如下兩種優勢。

  • 程式碼更安全。程式在編譯階段就可以檢查需要訪問的Class物件是否存在。
  • 程式效能更好。因為這種方式無須呼叫方法,所以效能更好。

也就是說,大部分時候我們都應該使用第二種方式來獲取指定類的Class物件。但如果我們只有一個字串,例如“java.lang.String”,若需要獲取該字串對應的Class物件,則只能使用第一種方式,使用Class的forName(String clazzName)方法獲取Class物件時,該方法可能丟擲一個ClassNotFoundException異常。一旦獲得了某個類所對應的Class物件之後,程式就可以呼叫Class物件的方法來獲得該物件和該類的真實資訊了。

4. Class類 API介紹

通過class類我們能夠獲取大量的資訊:

  1. 獲取建構函式
  • Connstructor getConstructor(Class<?>... parameterTypes):返回此Class物件對應類的指定public構造器。
  • Constructor<?>[] getConstructors():返回此Class物件對應類的所有public構造器。
  • Constructor getDeclaredConstructor(Class<?>... parameterTypes):返回此Class物件對應類的指定構造器,與構造器的訪問許可權無關。
  • Constructor<?>[] getDeclaredConstructors():返回此Class物件對應類的所有構造器,與構造器的訪問許可權無關。
  1. 獲取方法
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回此Class物件對應類的指定方法,與方法的訪問許可權無關。
  • Method[] getDeclaredMethods():返回此Class物件對應類的全部方法,與方法的訪問許可權無關。
  1. 獲取屬性
  • Field getField(String name):返回此Class物件對應類的指定public Field。
  • Field[] getFields():返回此Class物件對應類的所有public Field。
  • Field getDeclaredField(String name):返回此Class物件對應類的指定Field,與Field的訪問許可權無關。
  • Field[] getDeclaredFields():返回此Class物件對應類的全部Field,與Field的訪問許可權無關。
  1. 獲取Class對應類上所包含的Annotation。
  1. 獲取Class物件對應類包含的內部類。
  • Class<?>[] getDeclaredClasses():返回該Class物件對應類裡包含的全部內部類。
    如下方法用於訪問該Class物件對應類所在的外部類。
  • Class<?> getDeclaringClass():返回該Class物件對應類所在的外部類。
    如下方法用於訪問該Class物件對應類所繼承的父類、所實現的介面等。
  • Class<?>[] getInterfaces():返回該Class物件對應類所實現的全部介面。
  1. 獲取Class物件對應類所繼承的父類
  • Class<? super T> getSuperclass():返回該Class物件對應類的超類的Class物件。
  1. 獲取Class物件對應類的修飾符、所在包、類名等基本資訊。
  • int getModifiers():返回此類或介面的所有修飾符。修飾符由public、protected、private、final、static、abstract等對應的常量組成,返回的整數應使用Modifier工具類的方法來解碼,才可以獲取真實的修飾符。
  • Package getPackage():獲取此類的包。
  • String getName():以字串形式返回此Class物件所表示的類的名稱。
  • String getSimpleName():以字串形式返回此Class物件所表示的類的簡稱。
  1. 判斷該類是否為介面、列舉、註釋型別等
  • boolean isAnnotation():返回此Class物件是否表示一個註釋型別(由@interface定義)。
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判斷此Class物件是否使用了Annotation註釋修飾。
  • boolean isAnonymousClass():返回此Class物件是否是一個匿名類。
  • boolean isArray():返回此Class物件是否表示一個陣列類。
  • boolean isEnum():返回此Class物件是否表示一個列舉(由enum關鍵字定義)。
  • boolean isInterface():返回此Class物件是否表示一個介面(使用interface定義)。
  • boolean isInstance(Object obj):判斷obj是否是此Class物件的例項,該方法可以完全代替instanceof操作符。

上面的多個getMethod()方法和getConstructor()方法中,都需要傳入多個型別為Class<?>的引數,用於獲取指定的方法或指定的構造器。關於這個引數的作用,假設某個類內包含如下3個info方法簽名:

  • public void info()
  • public void info(String str)
  • public void info(String str , Integer num)

這3個同名方法屬於過載,它們的方法名相同,但引數列表不同。在Java語言中要確定一個方法光有方法名是不行的,例如,我們指定info方法——實際上可以是上面3個方法中的任意一個!如果需要確定一個方法,則應該由方法名和形參列表來確定,但形參名沒有任何實際意義,所以只能由形參型別來確定。例如,我們想要確定第二個info方法,則必須指定方法名為info,形參列表為String.class——因此在程式中獲取該方法使用如下程式碼:

clazz.getMethod("info",String.class);

使用反射生成物件

  1. 利用建構函式生成物件
  • 使用Class物件的newInstance()方法來建立該Class物件對應類的例項,這種方式要求該Class物件的對應類有預設構造器,而執行newInstance()方法時實際上是利用預設構造器來建立該類的例項。
  • 先使用Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立該Class物件對應類的例項。通過這種方式可以選擇使用指定的構造器來建立例項。
Constructor c = clazz.getConstructor(String.class);
c.newInstance("xx");

呼叫方法

Method setProName = aClass.getDeclaredMethod("setProName",String.class);
setProName.setAccessible(true);
etProName.invoke(product,"我是一個產品");

操作屬性

Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("fieldName:"+declaredField.getName()+" filedType:"+declaredField.getType());
        }
        Field proName = aClass.getDeclaredField("proName");
        proName.setAccessible(true);
        proName.set(product,"我是一個產品");
        System.out.println("修稿屬性:"+product);

運算元組

//使用反射動態地建立陣列
//建立一個元素型別為String,長度為3的陣列
Object arr = Array.newInstance(String.class, 3);
//依次為arr陣列中index為0,1,2的元素賦值
Array.set(arr, 0, "榮耀盒子");
Array.set(arr, 1, "榮耀8手機");
Array.set(arr, 2, "華為mate9保時捷版");
Object o1= Array.get(arr, 0);
Object o2= Array.get(arr, 1);
Object o3= Array.get(arr, 2);
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);

5. 使用Demo

public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        Class<Product> aClass = Product.class;
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor.getName());
        }
        Constructor<Product> constructor = aClass.getConstructor(int.class, String.class);
        Product product = constructor.newInstance(10, "ds");
        System.out.println("建立物件:"+product);

        //獲取方法並呼叫
        Method setProName = aClass.getDeclaredMethod("setProName", String.class);
        setProName.setAccessible(true);
        setProName.invoke(product,"我是一個產品");
        System.out.println("呼叫方法:"+product);

        //獲取屬性,並設定屬性的值
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("fieldName:"+declaredField.getName()+" filedType:"+declaredField.getType());
        }
        Field proName = aClass.getDeclaredField("proName");
        proName.setAccessible(true);
        proName.set(product,"我是一個產品");
        System.out.println("修稿屬性:"+product);

        //使用反射動態地建立陣列
        //建立一個元素型別為String,長度為3的陣列
        Object arr = Array.newInstance(String.class, 3);
        //依次為arr陣列中index為0,1,2的元素賦值
        Array.set(arr, 0, "榮耀盒子");
        Array.set(arr, 1, "榮耀8手機");
        Array.set(arr, 2, "華為mate9保時捷版");
        Object o1= Array.get(arr, 0);
        Object o2= Array.get(arr, 1);
        Object o3= Array.get(arr, 2);
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o3);
        
        System.out.println("end...");
    }
}

相關文章