Java編程的邏輯 (84) - 反射

?本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》,由機械工業出版社華章分社出版,于2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買,京東自營鏈接:http://item.jd.com/12299018.html


上節介紹完了并發,從本節開始,我們來探討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 {//使用反射清空所有ThreadLocalField 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() {@Overridepublic 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;}@Overridepublic 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編程及計算機技術的本質。用心原創,保留所有版權。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/258876.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/258876.shtml
英文地址,請注明出處:http://en.pswp.cn/news/258876.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# 與 VC Dll 傳輸信息

考慮&#xff1a; 使用string類型傳送&#xff1b; 在VC Dll中解析字符&#xff1b; 使用 string 類型將解析的類型傳送到C#程序中&#xff1b; 建立VC解析的函數&#xff0c;提高代碼可重用性轉載于:https://www.cnblogs.com/ein-key5205/p/3597612.html

linux下python_linux下python安裝

Python2.5的安裝方法&#xff1a; 1&#xff0e;下載源代碼 http://www.python.org/ftp/python/2.5.2/Python-2.5.2.tar.bz2 2&#xff0e; 安裝 $ tar –jxvf Python-2.5.2.tar.bz2 $ cd Python-2.5.2 $ ./configure $ make $ make install 3. 測試 在命令行下輸入python&…

灰度圖像的8位平面分解

所謂灰度圖像&#xff0c;即指8位256顏色的圖像。將圖像的每一位分別取出來&#xff0c;我們就可以將一幅圖像分解開來&#xff0c;形成8幅圖像。下面我們分別介紹使用matlab分解圖像與使用halcon/c分解圖像的方法。 matlab8位分解 clc; clear all; A imread(lena.tif); % 顯…

Win10 UAP 綁定

Compiled DataBinding in Windows Universal Applications (UAP) http://nicksnettravels.builttoroam.com/post/2015/04/26/Compiled-DataBinding-in-Windows-Universal-Applications-(UAP).aspx 讀寫剪貼板 http://www.cnphp6.com/archives/80079 Learn how the Reversi samp…

HDUOJ----4501小明系列故事——買年貨(三維背包)

小明系列故事——買年貨 Time Limit: 5000/2000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)Total Submission(s): 2146 Accepted Submission(s): 953 Problem Description春節將至&#xff0c;小明要去超市購置年貨&#xff0c;于是小明去了自己經常去…

css 橫線_atom.css正式發布,從此跟CSS框架說拜拜。

atom.css 大家好&#xff0c;我寫了一個css庫atom.css&#xff0c;蠻好用的&#xff0c;所以忍不住分享給大家。(https://github.com/MatrixAge/atom.css)起因寫HTML幾年了&#xff0c;再到如今的JSX&#xff0c;最大的感受不是枯燥&#xff0c;而是眼花。寫樣式的時候&#xf…

halcon模板匹配學習(一) Matching 初印象

什么是模板匹配呢&#xff1f;簡單而言&#xff0c;就是在圖像中尋找目標圖像&#xff08;模板&#xff09;&#xff0c;或者說&#xff0c;就是在圖像中尋找與模板圖像相似部分的一種圖像處理技術。依賴于選擇的方法不同&#xff0c;模板匹配可以處理各種情形下的變換&#xf…

第五章 面向方面編程___AOP入門

上一篇講了 AOP 和 OOP 的區別&#xff0c;這一次我們開始入門 AOP 。實現面向方面編程的技術&#xff0c;主要分為兩大類&#xff1a; 一是 采用動態代理技術&#xff0c;利用截取消息的方式&#xff0c;對該消息進行裝飾&#xff0c;以取代原有對象行為的執行&#xff1b; 二…

java將xml中的標簽名稱轉為小寫_深入學習Java Web(七): JSTL標簽庫

本文轉自與博客園一杯涼茶的博客.在之前我們學過在JSP頁面上為了不使用腳本&#xff0c;所以我們有了JSP內置的行為、行為只能提供一小部分的功能&#xff0c;大多數的時候還是會用java腳本&#xff0c;接著就使用了EL表達式&#xff0c;基本上EL表達式看似能滿足我們的要求&am…

python中*args和**args的不同

上一段代碼&#xff0c;大家感受一下 def test_param(*args): print(args) def test_param2(**args): print(args) test_param(test1,test2) >>>(test1,test2) test_param2(p1test1,p2test2) >>>{p1:test1, p2:test2} python提供了兩種特別的方法來定義函數的…

halcon模板匹配學習(二) 準備模板

如下&#xff0c;我們將介紹匹配的第一個操作&#xff1a;準備模板 初始時刻&#xff0c;我們準備好參考圖像&#xff0c;并對其做一定的處理&#xff0c;然后我們需要從參考圖像中導出模板&#xff0c;也就是將參考圖像裁剪成所謂的模板圖像。獲取模板圖像可以通過設置ROI來完…

揭秘繼承技術之虛函數

虛函數 調用虛函數時函數行為將根據對象所屬類的不同而變化。 父類指針或引用指向子類對象時&#xff0c;可訪問子類重寫方法&#xff08; virtual函數&#xff09;但無法訪問在父類中沒有定義的子類方法和數據成員。 #include <iostream>using namespace std;class Supe…

java中GET方式提交和POST方式提交

java中GET方式提交的示例&#xff1a; /*** 獲取關注列表;* return*/SuppressWarnings("unchecked")public static ArrayList<String> getUserList() {StringBuffer bufferRes new StringBuffer();ArrayList<String> users null;try {URL realUrl new…

基于HALCON的模板匹配方法總結

很早就想總結一下前段時間學習HALCON的心得&#xff0c;但由于其他的事情總是抽不出時間。去年有過一段時間的集中學習&#xff0c;做了許多的練習和實驗&#xff0c;并對基于HDevelop的形狀匹配算法的參數優化進行了研究&#xff0c;寫了一篇《基于HDevelop的形狀匹配算法參數…

js 數組移除_2020前端面試--常見的js面試題

&#xff08;答案持續更新...&#xff09; 1.簡述同步和異步的區別js是一門單線程語言&#xff0c;所謂"單線程"&#xff0c;就是指一次只能完成一件任務。如果有多個任務&#xff0c;就必須排隊&#xff0c;前面一個任務完成&#xff0c;再執行后面一個任務&#xf…

spring-自動加載配置文件\使用屬性文件注入

在上一篇jsf環境搭建的基礎上 , 加入spring框架 , 先看下目錄結構 src/main/resources 這個source folder 放置web項目所需的主要配置,打包時,會自動打包到WEB-INF下 首先看下pom.xml,需要引入一些依賴項: 1 <project xmlns"http://maven.apache.org/POM/4.0.0" x…

pygame碰撞檢測

最近在學Pygame,花一段時間做了一個異常簡陋版的"打磚塊". 這次重點說一下困擾我比較長時間的碰撞檢測(個人太菜..). 按照網上教程比較普遍的方法(也可能是我沒看見別的),碰撞檢測依次計算移動物體與被碰撞物體各個邊之間坐標是否相交.例如下列代碼,檢測小球與窗口的…

2017-5-4 進程

進程&#xff1a;一個應用程序就是一個進程開啟某個進程Process.Start("文件縮寫名");通過絕對路徑開啟某個進程Process p new Process();p.StartInfo new ProcessStartInfo("要打開的程序絕對路徑");p.Start(); 獲取全部開啟的進程&#xff1a;Process.…

很有用的cv牛人的網址和主要貢獻

CV人物1&#xff1a;Jianbo Shi史建波畢業于UC Berkeley&#xff0c;導師是Jitendra Malik。其最有影響力的研究成果&#xff1a;圖像分割。其于2000年在PAMI上多人合作發表"Noramlized cuts and image segmentation"。這是圖像分割領域內最經典的算法。主頁&#xf…

c++分治法求最大最小值實現_程序員:算法導論,分治法、歸并排序,偽代碼和Java實現...

分治法我們首先先介紹分治法。分治法的思想&#xff1a;將原問題分解為幾個規模較小但類似于原問題的子問題&#xff0c;遞歸地求解這些子問題&#xff0c;然后在合并這些子問題的解來解決原問題的解。還是拿撲克牌舉例子&#xff0c;假設桌上有兩堆牌面朝上的牌(牌面朝上&…