本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html
上節介紹完了併發,從本節開始,咱們來探討Java中的一些動態特性,包括反射、類加載器、註解和動態代理等。利用這些特性,能夠以優雅的方式實現一些靈活和通用的功能,常常用於各類框架、庫和系統程序中,好比:java
本節先來看反射機制。git
在通常操做數據的時候,咱們都是知道而且依賴於數據的類型的,好比:程序員
編譯器也是根據類型,進行代碼的檢查編譯。github
反射不同,它是在運行時,而非編譯時,動態獲取類型的信息,好比接口信息、成員信息、方法信息、構造方法信息等,根據這些動態獲取到的信息建立對象、訪問/修改爲員、調用方法等。這麼說比較抽象,下面咱們會具體來講明,反射的入口是名稱爲"Class"的類,咱們來看下。編程
咱們在17節介紹過類和繼承的基本實現原理,咱們提到,每一個已加載的類在內存都有一份類信息,每一個對象都有指向它所屬類信息的引用。Java中,類信息對應的類就是java.lang.Class,注意不是小寫的class,class是定義類的關鍵字,全部類的根父類Object有一個方法,能夠獲取對象的Class對象:swift
public final native Class<?> getClass()
複製代碼
Class是一個泛型類,有一個類型參數,getClass()並不知道具體的類型,因此返回Class<?>。數組
獲取Class對象不必定須要實例對象,若是在寫程序時就知道類名,可使用<類名>.class獲取Class對象,好比:安全
Class<Date> cls = Date.class;
複製代碼
接口也有Class對象,且這種方式對於接口也是適用的,好比:bash
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對象,它究竟是什麼類型呢?能夠經過如下方法進行檢查:
//是不是數組
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的其餘子接口還有:
咱們看一個簡單的示例:
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>
複製代碼
代碼比較簡單,咱們就不贅述了。
反射雖然是靈活的,但通常狀況下,並非咱們優先建議的,主要緣由是:
簡單的說,若是能用接口實現一樣的靈活性,就不要使用反射。
本節介紹了Java中反射相關的主要類和方法,經過入口類Class,能夠訪問類的各類信息,如字段、方法、構造方法、父類、接口、泛型信息等,也能夠建立和操做對象,調用方法等,利用這些方法,能夠編寫通用的、動態靈活的程序,本節演示了一個簡單的通用序列化/反序列化類SimpleMapper。反射雖然是靈活通用的,但它更容易出現運行時錯誤,因此,能用接口代替的時候,應該儘可能使用接口。
本節介紹的不少類如Class/Field/Method/Constructor均可以有註解,註解究竟是什麼呢?
(與其餘章節同樣,本節全部代碼位於 github.com/swiftma/pro…,位於包shuo.laoma.dynamic.c84下)
未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),從入門到高級,深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。用心原創,保留全部版權。