1. 程式人生 > >計算機程序的思維邏輯 (84) - 反射

計算機程序的思維邏輯 (84) - 反射

native 掃描 col padding nbsp cast 靜態 sna n)

上節介紹完了並發,從本節開始,我們來探討Java中的一些動態特性,包括反射、類加載器、註解和動態代理等。利用這些特性,可以以優雅的方式實現一些靈活和通用的功能,經常用於各種框架、庫和系統程序中,比如:

  • 在63節介紹的實用序列化庫Jackson,利用反射和註解實現了通用的序列化/反序列化機制
  • 有多種庫如Spring MVC, Jersey用於處理Web請求,利用反射和註解,能方便的將用戶的請求參數和內容轉換為Java對象,將Java對象轉變為響應內容
  • 有多種庫如Spring, Guice利用這些特性實現了對象管理容器,方便程序員管理對象的生命周期以及其中復雜的依賴關系
  • 應用服務器比如Tomcat利用類加載器實現不同應用之間的隔離、JSP技術也利用類加載器實現修改代碼不用重啟就能生效的特性
  • 面向方面的編程(AOP - Aspect Oriented Programming)將編程中通用的關註點比如日誌記錄、安全檢查等與業務的主體邏輯相分離,減少冗余代碼,提高程序的可維護性,AOP需要依賴上面的這些特性來實現

本節先來看反射機制。

在一般操作數據的時候,我們都是知道並且依賴於數據的類型的,比如:

  • 根據類型使用new創建對象
  • 根據類型定義變量,類型可能是基本類型、類、接口或數組
  • 將特定類型的對象傳遞給方法
  • 根據類型訪問對象的屬性,調用對象的方法

編譯器也是根據類型,進行代碼的檢查編譯。

反射不一樣,它是在運行時,而非編譯時,動態獲取類型的信息,比如接口信息、成員信息、方法信息、構造方法信息等,根據這些動態獲取到的信息創建對象、訪問/修改成員、調用方法等。這麽說比較抽象,下面我們會具體來說明,反射的入口是名稱為"Class"的類,我們來看下。

"Class"類

獲取Class對象

我們在17節介紹過類和繼承的基本實現原理,我們提到,每個已加載的類在內存都有一份類信息,每個對象都有指向它所屬類信息的引用。Java中,類信息對應的類就是java.lang.Class,註意不是小寫的class,class是定義類的關鍵字,所有類的根父類Object有一個方法,可以獲取對象的Class對象:

public final native Class<?> getClass()

Class是一個泛型類,有一個類型參數,getClass()並不知道具體的類型,所以返回Class<?>。

獲取Class對象不一定需要實例對象,如果在寫程序時就知道類名,可以使用<類名>.class獲取Class對象,比如:

Class<Date> cls = Date.class;

接口也有Class對象,且這種方式對於接口也是適用的,比如:

Class<Comparable> cls = Comparable.class;

基本類型沒有getClass方法,但也都有對應的Class對象,類型參數為對應的包裝類型,比如:

Class<Integer> intCls = int.class;
Class<Byte> byteCls = byte.class;
Class<Character> charCls = char.class;
Class<Double> doubleCls = double.class;

void作為特殊的返回類型,也有對應的Class:

Class<Void> voidCls = void.class;

對於數組,每種類型都有對應數組類型的Class對象,每個維度都有一個,即一維數組有一個,二維數組有一個不同的,比如:

String[] strArr = new String[10];
int[][] twoDimArr = new int[3][2];
int[] oneDimArr = new int[10];
Class<? extends String[]> strArrCls = strArr.getClass();
Class<? extends int[][]> twoDimArrCls = twoDimArr.getClass();
Class<? extends int[]> oneDimArrCls = oneDimArr.getClass();

枚舉類型也有對應的Class,比如:

enum Size {
    SMALL, MEDIUM, BIG
}

Class<Size> cls = Size.class;

Class有一個靜態方法forName,可以根據類名直接加載Class,獲取Class對象,比如:

try {
    Class<?> cls = Class.forName("java.util.HashMap");
    System.out.println(cls.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

註意forName可能拋出異常ClassNotFoundException。

有了Class對象後,我們就可以了解到關於類型的很多信息,並基於這些信息采取一些行動,Class的方法很多,大部分比較簡單直接,容易理解,下面,我們分為若幹組,進行簡要介紹。

名稱信息

Class有如下方法,可以獲取與名稱有關的信息:

public String getName()
public String getSimpleName()
public String getCanonicalName()
public Package getPackage()

getSimpleName不帶包信息,getName返回的是Java內部使用的真正的名字,getCanonicalName返回的名字更為友好,getPackage返回的是包信息,它們的不同可以看如下表格:

技術分享

需要說明的是數組類型的getName返回值,它使用前綴[表示數組,有幾個[表示是幾維數組,數組的類型用一個字符表示,I表示int,L表示類或接口,其他類型與字符的對應關系為: boolean(Z), byte(B), char(C), double(D), float(F), long(J), short(S),對於引用類型的數組,註意最後有一個分號";"。

字段(實例和靜態變量)信息

類中定義的靜態和實例變量都被稱為字段,用類Field表示,位於包java.util.reflect下,後文涉及到的反射相關的類都位於該包下,Class有四個獲取字段信息的方法:

//返回所有的public字段,包括其父類的,如果沒有字段,返回空數組
public Field[] getFields()
//返回本類聲明的所有字段,包括非public的,但不包括父類的
public Field[] getDeclaredFields()
//返回本類或父類中指定名稱的public字段,找不到拋出異常NoSuchFieldException
public Field getField(String name)
//返回本類中聲明的指定名稱的字段,找不到拋出異常NoSuchFieldException
public Field getDeclaredField(String name)

Field也有很多方法,可以獲取字段的信息,也可以通過Field訪問和操作指定對象中該字段的值,基本方法有:

//獲取字段的名稱
public String getName()
//判斷當前程序是否有該字段的訪問權限
public boolean isAccessible()
//flag設為true表示忽略Java的訪問檢查機制,以允許讀寫非public的字段
public void setAccessible(boolean flag)
//獲取指定對象obj中該字段的值
public Object get(Object obj)
//將指定對象obj中該字段的值設為value
public void set(Object obj, Object value)

在get/set方法中,對於靜態變量,obj被忽略,可以為null,如果字段值為基本類型,get/set會自動在基本類型與對應的包裝類型間進行轉換,對於private字段,直接調用get/set會拋出非法訪問異常IllegalAccessException,應該先調用setAccessible(true)以關閉Java的檢查機制。

看段簡單的示例代碼:

List<String> obj = Arrays.asList(new String[]{"老馬","編程"});
Class<?> cls = obj.getClass();
for(Field f : cls.getDeclaredFields()){
    f.setAccessible(true);
    System.out.println(f.getName()+" - "+f.get(obj));
}

代碼比較簡單,就不贅述了。我們在ThreadLocal一節介紹過利用反射來清空ThreadLocal,這裏重復下其代碼,含義就比較清楚了:

protected void beforeExecute(Thread t, Runnable r) {
    try {
        //使用反射清空所有ThreadLocal
        Field f = t.getClass().getDeclaredField("threadLocals");
        f.setAccessible(true);
        f.set(t, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
    super.beforeExecute(t, r);
}

除了以上方法,Field還有很多別的方法,比如:

//返回字段的修飾符
public int getModifiers()
//返回字段的類型
public Class<?> getType()
//以基本類型操作字段
public void setBoolean(Object obj, boolean z)
public boolean getBoolean(Object obj)
public void setDouble(Object obj, double d)
public double getDouble(Object obj)

//查詢字段的註解信息
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
public Annotation[] getDeclaredAnnotations()

getModifiers返回的是一個int,可以通過Modifier類的靜態方法進行解讀,比如,假定Student類有如下字段:

public static final int MAX_NAME_LEN = 255;

可以這樣查看該字段的修飾符:

Field f = Student.class.getField("MAX_NAME_LEN");
int mod = f.getModifiers();
System.out.println(Modifier.toString(mod));
System.out.println("isPublic: " + Modifier.isPublic(mod));
System.out.println("isStatic: " + Modifier.isStatic(mod));
System.out.println("isFinal: " + Modifier.isFinal(mod));
System.out.println("isVolatile: " + Modifier.isVolatile(mod));

輸出為:

public static final
isPublic: true
isStatic: true
isFinal: true
isVolatile: false

關於註解,我們下節再詳細介紹。

方法信息

類中定義的靜態和實例方法都被稱為方法,用類Method表示,Class有四個獲取方法信息的方法:

//返回所有的public方法,包括其父類的,如果沒有方法,返回空數組
public Method[] getMethods()
//返回本類聲明的所有方法,包括非public的,但不包括父類的
public Method[] getDeclaredMethods()
//返回本類或父類中指定名稱和參數類型的public方法,找不到拋出異常NoSuchMethodException
public Method getMethod(String name, Class<?>... parameterTypes)
//返回本類中聲明的指定名稱和參數類型的方法,找不到拋出異常NoSuchMethodException
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

Method也有很多方法,可以獲取方法的信息,也可以通過Method調用對象的方法,基本方法有:

//獲取方法的名稱
public String getName()
//flag設為true表示忽略Java的訪問檢查機制,以允許調用非public的方法
public void setAccessible(boolean flag)
//在指定對象obj上調用Method代表的方法,傳遞的參數列表為args
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

對invoke方法,如果Method為靜態方法,obj被忽略,可以為null,args可以為null,也可以為一個空的數組,方法調用的返回值被包裝為Object返回,如果實際方法調用拋出異常,異常被包裝為InvocationTargetException重新拋出,可以通過getCause方法得到原異常。

看段簡單的示例代碼:

Class<?> cls = Integer.class;
try {
    Method method = cls.getMethod("parseInt", new Class[]{String.class});
    System.out.println(method.invoke(null, "123"));
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

Method還有很多方法,可以獲取方法的修飾符、參數、返回值、註解等信息,比如:

//獲取方法的修飾符,返回值可通過Modifier類進行解讀
public int getModifiers()
//獲取方法的參數類型
public Class<?>[] getParameterTypes()
//獲取方法的返回值類型
public Class<?> getReturnType()
//獲取方法聲明拋出的異常類型
public Class<?>[] getExceptionTypes()
//獲取註解信息
public Annotation[] getDeclaredAnnotations()
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
//獲取方法參數的註解信息
public Annotation[][] getParameterAnnotations() 

創建對象和構造方法

Class有一個方法,可以用它來創建對象:

public T newInstance() throws InstantiationException, IllegalAccessException

它會調用類的默認構造方法(即無參public構造方法),如果類沒有該構造方法,會拋出異常InstantiationException。看個簡單示例:

Map<String,Integer> map = HashMap.class.newInstance();
map.put("hello", 123);

很多利用反射的庫和框架都默認假定類有無參public構造方法,所以當類利用這些庫和框架時要記住提供一個。

newInstance只能使用默認構造方法,Class還有一些方法,可以獲取所有的構造方法:

//獲取所有的public構造方法,返回值可能為長度為0的空數組
public Constructor<?>[] getConstructors()
//獲取所有的構造方法,包括非public的
public Constructor<?>[] getDeclaredConstructors()
//獲取指定參數類型的public構造方法,沒找到拋出異常NoSuchMethodException
public Constructor<T> getConstructor(Class<?>... parameterTypes)
//獲取指定參數類型的構造方法,包括非public的,沒找到拋出異常NoSuchMethodException
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 

類Constructor表示構造方法,通過它可以創建對象,方法為:

public T newInstance(Object ... initargs) throws InstantiationException, 
IllegalAccessException, IllegalArgumentException, InvocationTargetException

比如:

Constructor<StringBuilder> contructor= StringBuilder.class
                    .getConstructor(new Class[]{int.class});
StringBuilder sb = contructor.newInstance(100);

除了創建對象,Constructor還有很多方法,可以獲取關於構造方法的很多信息,比如:

//獲取參數的類型信息
public Class<?>[] getParameterTypes()
//構造方法的修飾符,返回值可通過Modifier類進行解讀
public int getModifiers()
//構造方法的註解信息
public Annotation[] getDeclaredAnnotations()
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
//構造方法中參數的註解信息
public Annotation[][] getParameterAnnotations() 

類型檢查和轉換

我們在16節介紹過instanceof關鍵字,它可以用來判斷變量指向的實際對象類型,instanceof後面的類型是在代碼中確定的,如果要檢查的類型是動態的,可以使用Class類的如下方法:

public native boolean isInstance(Object obj)

也就是說,如下代碼:

if(list instanceof ArrayList){
    System.out.println("array list");
}

和下面代碼的輸出是相同的:

Class cls = Class.forName("java.util.ArrayList");
if(cls.isInstance(list)){
    System.out.println("array list");
}

除了判斷類型,在程序中也往往需要進行強制類型轉換,比如:

List list = ..
if(list instanceof ArrayList){
    ArrayList arrList = (ArrayList)list;
}

在這段代碼中,強制轉換到的類型是在寫代碼時就知道的,如果是動態的,可以使用Class的如下方法:

public T cast(Object obj)

比如:

public static <T> T toType(Object obj, Class<T> cls){
    return cls.cast(obj);
}

isInstance/cast描述的都是對象和類之間的關系,Class還有一個方法,可以判斷Class之間的關系:

// 檢查參數類型cls能否賦給當前Class類型的變量
public native boolean isAssignableFrom(Class<?> cls);

比如,如下表達式的結果都為true:

Object.class.isAssignableFrom(String.class)
String.class.isAssignableFrom(String.class)
List.class.isAssignableFrom(ArrayList.class)

Class的類型信息

Class代表的類型既可以是普通的類、也可以是內部類,還可以是基本類型、數組等,對於一個給定的Class對象,它到底是什麽類型呢?可以通過以下方法進行檢查:

//是否是數組
public native boolean isArray();  
//是否是基本類型
public native boolean isPrimitive();
//是否是接口
public native boolean isInterface();
//是否是枚舉
public boolean isEnum()
//是否是註解
public boolean isAnnotation()
//是否是匿名內部類
public boolean isAnonymousClass()
//是否是成員類
public boolean isMemberClass()
//是否是本地類
public boolean isLocalClass() 

需要說明下匿名內部類、成員類與本地類的區別,本地類是指在方法內部定義的非匿名內部類,比如,如下代碼:

public static void localClass(){
    class MyLocal {
    }
    Runnable r = new Runnable() {
        @Override
        public void run(){
            
        }
    };
    System.out.println(MyLocal.class.isLocalClass());
    System.out.println(r.getClass().isLocalClass());
}

MyLocal定義在localClass方法內部,就是一個本地類,r的對象所屬的類是一個匿名類,但不是本地類。

成員類也是內部類,定義在類內部、方法外部,它不是匿名類,也不是本地類。

類的聲明信息

Class還有很多方法,可以獲取類的聲明信息,如修飾符、父類、實現的接口、註解等,如下所示:

//獲取修飾符,返回值可通過Modifier類進行解讀
public native int getModifiers()
//獲取父類,如果為Object,父類為null
public native Class<? super T> getSuperclass()
//對於類,為自己聲明實現的所有接口,對於接口,為直接擴展的接口,不包括通過父類間接繼承來的
public native Class<?>[] getInterfaces();
//自己聲明的註解
public Annotation[] getDeclaredAnnotations()
//所有的註解,包括繼承得到的
public Annotation[] getAnnotations()
//獲取或檢查指定類型的註解,包括繼承得到的
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

內部類

關於內部類,Class有一些專門的方法,比如:

//獲取所有的public的內部類和接口,包括從父類繼承得到的
public Class<?>[] getClasses()
//獲取自己聲明的所有的內部類和接口
public Class<?>[] getDeclaredClasses()
//如果當前Class為內部類,獲取聲明該類的最外部的Class對象
public Class<?> getDeclaringClass()
//如果當前Class為內部類,獲取直接包含該類的類
public Class<?> getEnclosingClass()
//如果當前Class為本地類或匿名內部類,返回包含它的方法
public Method getEnclosingMethod()

類的加載

Class有兩個靜態方法,可以根據類名加載類:

public static Class<?> forName(String className)
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)

ClassLoader表示類加載器,後面章節我們會進一步介紹,initialize表示加載後,是否執行類的初始化代碼(如static語句塊)。第一個方法中沒有傳這些參數,相當於調用:

Class.forName(className, true, currentLoader)

currentLoader表示加載當前類的ClassLoader。

這裏className與Class.getName的返回值是一致的,比如,對於String數組:

String name = "[Ljava.lang.String;";
Class cls = Class.forName(name);
System.out.println(cls == String[].class);

需要註意的是,基本類型不支持forName方法,也就是說,如下寫法:

Class.forName("int");

會拋出異常ClassNotFoundException,那如何根據原始類型的字符串構造Class對象呢?可以對Class.forName進行一下包裝,比如:

public static Class<?> forName(String className) throws ClassNotFoundException{
    if("int".equals(className)){
        return int.class;
    }
    //其他基本類型...
    return Class.forName(className);
}

反射與數組

對於數組類型,有一個專門的方法,可以獲取它的元素類型:

public native Class<?> getComponentType()

比如:

String[] arr = new String[]{};
System.out.println(arr.getClass().getComponentType());

輸出為:

class java.lang.String

java.lang.reflect包中有一個針對數組的專門的類Array(註意不是java.util中的Arrays),提供了對於數組的一些反射支持,以便於統一處理多種類型的數組,主要方法有:

//創建指定元素類型、指定長度的數組,
public static Object newInstance(Class<?> componentType, int length)
//創建多維數組
public static Object newInstance(Class<?> componentType, int... dimensions)
//獲取數組array指定的索引位置index處的值
public static native Object get(Object array, int index)
//修改數組array指定的索引位置index處的值為value
public static native void set(Object array, int index, Object value)
//返回數組的長度
public static native int getLength(Object array)

需要註意的是,在Array類中,數組是用Object而非Object[]表示的,這是為什麽呢?這是為了方便處理多種類型的數組,int[],String[]都不能與Object[]相互轉換,但可以與Object相互轉換,比如:

int[] intArr = (int[])Array.newInstance(int.class, 10);
String[] strArr = (String[])Array.newInstance(String.class, 10);

除了以Object類型操作數組元素外,Array也支持以各種基本類型操作數組元素,如:

public static native double getDouble(Object array, int index)
public static native void setDouble(Object array, int index, double d)
public static native void setLong(Object array, int index, long l)
public static native long getLong(Object array, int index)

反射與枚舉

枚舉類型也有一個專門方法,可以獲取所有的枚舉常量:

public T[] getEnumConstants()

應用示例

介紹了Class的這麽多方法,有什麽用呢?我們看個簡單的示例,利用反射實現一個簡單的通用序列化/反序列化類SimpleMapper,它提供兩個靜態方法:

public static String toString(Object obj)
public static Object fromString(String str)

toString將對象obj轉換為字符串,fromString將字符串轉換為對象。為簡單起見,我們只支持最簡單的類,即有默認構造方法,成員類型只有基本類型、包裝類或String。另外,序列化的格式也很簡單,第一行為類的名稱,後面每行表示一個字段,用字符‘=‘分隔,表示字段名稱和字符串形式的值。SimpleMapper可以這麽用:

public class SimpleMapperDemo {
    static class Student {
        String name;
        int age;
        Double score;

        public Student() {
        }

        public Student(String name, int age, Double score) {
            super();
            this.name = name;
            this.age = age;
            this.score = score;
        }

        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
        }
    }

    public static void main(String[] args) {
        Student zhangsan = new Student("張三", 18, 89d);
        String str = SimpleMapper.toString(zhangsan);
        Student zhangsan2 = (Student) SimpleMapper.fromString(str);
        System.out.println(zhangsan2);
    }
}

代碼先調用toString方法將對象轉換為了String,然後調用fromString方法將字符串轉換為了Student,新對象的值與原對象是一樣的,輸出如下所示:

Student [name=張三, age=18, score=89.0]

我們來看SimpleMapper的示例實現(主要用於演示原理,在生產中謹慎使用),toString的代碼為:

public static String toString(Object obj) {
    try {
        Class<?> cls = obj.getClass();
        StringBuilder sb = new StringBuilder();
        sb.append(cls.getName() + "\n");
        for (Field f : cls.getDeclaredFields()) {
            if (!f.isAccessible()) {
                f.setAccessible(true);
            }
            sb.append(f.getName() + "=" + f.get(obj).toString() + "\n");
        }
        return sb.toString();
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

fromString的代碼為:

public static Object fromString(String str) {
    try {
        String[] lines = str.split("\n");
        if (lines.length < 1) {
            throw new IllegalArgumentException(str);
        }
        Class<?> cls = Class.forName(lines[0]);
        Object obj = cls.newInstance();
        if (lines.length > 1) {
            for (int i = 1; i < lines.length; i++) {
                String[] fv = lines[i].split("=");
                if (fv.length != 2) {
                    throw new IllegalArgumentException(lines[i]);
                }
                Field f = cls.getDeclaredField(fv[0]);
                if(!f.isAccessible()){
                    f.setAccessible(true);
                }
                setFieldValue(f, obj, fv[1]);
            }
        }
        return obj;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

它調用了setFieldValue方法對字段設置值,其代碼為:

private static void setFieldValue(Field f, Object obj, String value) throws Exception {
    Class<?> type = f.getType();
    if (type == int.class) {
        f.setInt(obj, Integer.parseInt(value));
    } else if (type == byte.class) {
        f.setByte(obj, Byte.parseByte(value));
    } else if (type == short.class) {
        f.setShort(obj, Short.parseShort(value));
    } else if (type == long.class) {
        f.setLong(obj, Long.parseLong(value));
    } else if (type == float.class) {
        f.setFloat(obj, Float.parseFloat(value));
    } else if (type == double.class) {
        f.setDouble(obj, Double.parseDouble(value));
    } else if (type == char.class) {
        f.setChar(obj, value.charAt(0));
    } else if (type == boolean.class) {
        f.setBoolean(obj, Boolean.parseBoolean(value));
    } else if (type == String.class) {
        f.set(obj, value);
    } else {
        Constructor<?> ctor = type.getConstructor(new Class[] { String.class });
        f.set(obj, ctor.newInstance(value));
    }
}

setFieldValue根據字段的類型,將字符串形式的值轉換為了對應類型的值,對於基本類型和String以外的類型,它假定該類型有一個以String類型為參數的構造方法。

反射與泛型

在介紹泛型的時候,我們提到,泛型參數在運行時會被擦除,這裏,我們需要補充一下,在類信息Class中依然有關於泛型的一些信息,可以通過反射得到,泛型涉及到一些更多的方法和類,上面的介紹中進行了忽略,這裏簡要補充下。

Class有如下方法,可以獲取類的泛型參數信息:

public TypeVariable<Class<T>>[] getTypeParameters()

Field有如下方法:

public Type getGenericType()

Method有如下方法:

public Type getGenericReturnType()
public Type[] getGenericParameterTypes()
public Type[] getGenericExceptionTypes()

Constructor有如下方法:

public Type[] getGenericParameterTypes() 

Type是一個接口,Class實現了Type,Type的其他子接口還有:

  • TypeVariable:類型參數,可以有上界,比如:T extends Number
  • ParameterizedType:參數化的類型,有原始類型和具體的類型參數,比如:List<String>
  • WildcardType:通配符類型,比如:?, ? extends Number, ? super Integer

我們看一個簡單的示例:

public class GenericDemo {
    static class GenericTest<U extends Comparable<U>, V> {
        U u;
        V v;
        List<String> list;

        public U test(List<? extends Number> numbers) {
            return null;
        }
    }

    public static void main(String[] args) throws Exception {
        Class<?> cls = GenericTest.class;
        // 類的類型參數
        for (TypeVariable t : cls.getTypeParameters()) {
            System.out.println(t.getName() + " extends " + Arrays.toString(t.getBounds()));
        }

        // 字段 - 泛型類型
        Field fu = cls.getDeclaredField("u");
        System.out.println(fu.getGenericType());

        // 字段 - 參數化的類型
        Field flist = cls.getDeclaredField("list");
        Type listType = flist.getGenericType();
        if (listType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) listType;
            System.out.println("raw type: " + pType.getRawType() + ",type arguments:"
                    + Arrays.toString(pType.getActualTypeArguments()));
        }

        // 方法的泛型參數
        Method m = cls.getMethod("test", new Class[] { List.class });
        for (Type t : m.getGenericParameterTypes()) {
            System.out.println(t);
        }
    }
}

程序的輸出為:

U extends [java.lang.Comparable<U>]
V extends [class java.lang.Object]
U
raw type: interface java.util.List,type arguments:[class java.lang.String]
java.util.List<? extends java.lang.Number>

代碼比較簡單,我們就不贅述了。

慎用反射

反射雖然是靈活的,但一般情況下,並不是我們優先建議的,主要原因是:

  • 反射更容易出現運行時錯誤,使用顯式的類和接口,編譯器能幫我們做類型檢查,減少錯誤,但使用反射,類型是運行時才知道的,編譯器無能為力
  • 反射的性能要低一些,在訪問字段、調用方法前,反射先要查找對應的Field/Method,性能要慢一些

簡單的說,如果能用接口實現同樣的靈活性,就不要使用反射。

小結

本節介紹了Java中反射相關的主要類和方法,通過入口類Class,可以訪問類的各種信息,如字段、方法、構造方法、父類、接口、泛型信息等,也可以創建和操作對象,調用方法等,利用這些方法,可以編寫通用的、動態靈活的程序,本節演示了一個簡單的通用序列化/反序列化類SimpleMapper。反射雖然是靈活通用的,但它更容易出現運行時錯誤,所以,能用接口代替的時候,應該盡量使用接口。

本節介紹的很多類如Class/Field/Method/Constructor都可以有註解,註解到底是什麽呢?

(與其他章節一樣,本節所有代碼位於 https://github.com/swiftma/program-logic,位於包shuo.laoma.dynamic.c84下)

----------------

未完待續,查看最新文章,敬請關註微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及計算機技術的本質。用心原創,保留所有版權。

技術分享

計算機程序的思維邏輯 (84) - 反射