Java编程的逻辑 (84) - 反射
?本系列文章經(jīng)補(bǔ)充和完善,已修訂整理成書(shū)《Java編程的邏輯》,由機(jī)械工業(yè)出版社華章分社出版,于2018年1月上市熱銷(xiāo),讀者好評(píng)如潮!各大網(wǎng)店和書(shū)店有售,歡迎購(gòu)買(mǎi),京東自營(yíng)鏈接:http://item.jd.com/12299018.html
上節(jié)介紹完了并發(fā),從本節(jié)開(kāi)始,我們來(lái)探討Java中的一些動(dòng)態(tài)特性,包括反射、類(lèi)加載器、注解和動(dòng)態(tài)代理等。利用這些特性,可以以?xún)?yōu)雅的方式實(shí)現(xiàn)一些靈活和通用的功能,經(jīng)常用于各種框架、庫(kù)和系統(tǒng)程序中,比如:
- 在63節(jié)介紹的實(shí)用序列化庫(kù)Jackson,利用反射和注解實(shí)現(xiàn)了通用的序列化/反序列化機(jī)制
- 有多種庫(kù)如Spring MVC, Jersey用于處理Web請(qǐng)求,利用反射和注解,能方便的將用戶(hù)的請(qǐng)求參數(shù)和內(nèi)容轉(zhuǎn)換為Java對(duì)象,將Java對(duì)象轉(zhuǎn)變?yōu)轫憫?yīng)內(nèi)容
- 有多種庫(kù)如Spring, Guice利用這些特性實(shí)現(xiàn)了對(duì)象管理容器,方便程序員管理對(duì)象的生命周期以及其中復(fù)雜的依賴(lài)關(guān)系
- 應(yīng)用服務(wù)器比如Tomcat利用類(lèi)加載器實(shí)現(xiàn)不同應(yīng)用之間的隔離、JSP技術(shù)也利用類(lèi)加載器實(shí)現(xiàn)修改代碼不用重啟就能生效的特性
- 面向方面的編程(AOP - Aspect Oriented Programming)將編程中通用的關(guān)注點(diǎn)比如日志記錄、安全檢查等與業(yè)務(wù)的主體邏輯相分離,減少冗余代碼,提高程序的可維護(hù)性,AOP需要依賴(lài)上面的這些特性來(lái)實(shí)現(xiàn)
本節(jié)先來(lái)看反射機(jī)制。
在一般操作數(shù)據(jù)的時(shí)候,我們都是知道并且依賴(lài)于數(shù)據(jù)的類(lèi)型的,比如:
- 根據(jù)類(lèi)型使用new創(chuàng)建對(duì)象
- 根據(jù)類(lèi)型定義變量,類(lèi)型可能是基本類(lèi)型、類(lèi)、接口或數(shù)組
- 將特定類(lèi)型的對(duì)象傳遞給方法
- 根據(jù)類(lèi)型訪問(wèn)對(duì)象的屬性,調(diào)用對(duì)象的方法
編譯器也是根據(jù)類(lèi)型,進(jìn)行代碼的檢查編譯。
反射不一樣,它是在運(yùn)行時(shí),而非編譯時(shí),動(dòng)態(tài)獲取類(lèi)型的信息,比如接口信息、成員信息、方法信息、構(gòu)造方法信息等,根據(jù)這些動(dòng)態(tài)獲取到的信息創(chuàng)建對(duì)象、訪問(wèn)/修改成員、調(diào)用方法等。這么說(shuō)比較抽象,下面我們會(huì)具體來(lái)說(shuō)明,反射的入口是名稱(chēng)為"Class"的類(lèi),我們來(lái)看下。
"Class"類(lèi)
獲取Class對(duì)象
我們?cè)?span style="color:#000000;">17節(jié)介紹過(guò)類(lèi)和繼承的基本實(shí)現(xiàn)原理,我們提到,每個(gè)已加載的類(lèi)在內(nèi)存都有一份類(lèi)信息,每個(gè)對(duì)象都有指向它所屬類(lèi)信息的引用。Java中,類(lèi)信息對(duì)應(yīng)的類(lèi)就是java.lang.Class,注意不是小寫(xiě)的class,class是定義類(lèi)的關(guān)鍵字,所有類(lèi)的根父類(lèi)Object有一個(gè)方法,可以獲取對(duì)象的Class對(duì)象:
public final native Class<?> getClass()Class是一個(gè)泛型類(lèi),有一個(gè)類(lèi)型參數(shù),getClass()并不知道具體的類(lèi)型,所以返回Class<?>。
獲取Class對(duì)象不一定需要實(shí)例對(duì)象,如果在寫(xiě)程序時(shí)就知道類(lèi)名,可以使用<類(lèi)名>.class獲取Class對(duì)象,比如:
Class<Date> cls = Date.class;接口也有Class對(duì)象,且這種方式對(duì)于接口也是適用的,比如:
Class<Comparable> cls = Comparable.class;基本類(lèi)型沒(méi)有g(shù)etClass方法,但也都有對(duì)應(yīng)的Class對(duì)象,類(lèi)型參數(shù)為對(duì)應(yīng)的包裝類(lèi)型,比如:
Class<Integer> intCls = int.class; Class<Byte> byteCls = byte.class; Class<Character> charCls = char.class; Class<Double> doubleCls = double.class;void作為特殊的返回類(lèi)型,也有對(duì)應(yīng)的Class:
Class<Void> voidCls = void.class;對(duì)于數(shù)組,每種類(lèi)型都有對(duì)應(yīng)數(shù)組類(lèi)型的Class對(duì)象,每個(gè)維度都有一個(gè),即一維數(shù)組有一個(gè),二維數(shù)組有一個(gè)不同的,比如:
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();枚舉類(lèi)型也有對(duì)應(yīng)的Class,比如:
enum Size {SMALL, MEDIUM, BIG }Class<Size> cls = Size.class;Class有一個(gè)靜態(tài)方法forName,可以根據(jù)類(lèi)名直接加載Class,獲取Class對(duì)象,比如:
try {Class<?> cls = Class.forName("java.util.HashMap");System.out.println(cls.getName()); } catch (ClassNotFoundException e) {e.printStackTrace(); }注意forName可能拋出異常ClassNotFoundException。
有了Class對(duì)象后,我們就可以了解到關(guān)于類(lèi)型的很多信息,并基于這些信息采取一些行動(dòng),Class的方法很多,大部分比較簡(jiǎn)單直接,容易理解,下面,我們分為若干組,進(jìn)行簡(jiǎn)要介紹。
名稱(chēng)信息
Class有如下方法,可以獲取與名稱(chēng)有關(guān)的信息:
public String getName() public String getSimpleName() public String getCanonicalName() public Package getPackage()getSimpleName不帶包信息,getName返回的是Java內(nèi)部使用的真正的名字,getCanonicalName返回的名字更為友好,getPackage返回的是包信息,它們的不同可以看如下表格:
需要說(shuō)明的是數(shù)組類(lèi)型的getName返回值,它使用前綴[表示數(shù)組,有幾個(gè)[表示是幾維數(shù)組,數(shù)組的類(lèi)型用一個(gè)字符表示,I表示int,L表示類(lèi)或接口,其他類(lèi)型與字符的對(duì)應(yīng)關(guān)系為: boolean(Z), byte(B), char(C), double(D), float(F), long(J), short(S),對(duì)于引用類(lèi)型的數(shù)組,注意最后有一個(gè)分號(hào)";"。
字段(實(shí)例和靜態(tài)變量)信息
類(lèi)中定義的靜態(tài)和實(shí)例變量都被稱(chēng)為字段,用類(lèi)Field表示,位于包java.util.reflect下,后文涉及到的反射相關(guān)的類(lèi)都位于該包下,Class有四個(gè)獲取字段信息的方法:
//返回所有的public字段,包括其父類(lèi)的,如果沒(méi)有字段,返回空數(shù)組 public Field[] getFields() //返回本類(lèi)聲明的所有字段,包括非public的,但不包括父類(lèi)的 public Field[] getDeclaredFields() //返回本類(lèi)或父類(lèi)中指定名稱(chēng)的public字段,找不到拋出異常NoSuchFieldException public Field getField(String name) //返回本類(lèi)中聲明的指定名稱(chēng)的字段,找不到拋出異常NoSuchFieldException public Field getDeclaredField(String name)Field也有很多方法,可以獲取字段的信息,也可以通過(guò)Field訪問(wèn)和操作指定對(duì)象中該字段的值,基本方法有:
//獲取字段的名稱(chēng) public String getName() //判斷當(dāng)前程序是否有該字段的訪問(wèn)權(quán)限 public boolean isAccessible() //flag設(shè)為true表示忽略Java的訪問(wèn)檢查機(jī)制,以允許讀寫(xiě)非public的字段 public void setAccessible(boolean flag) //獲取指定對(duì)象obj中該字段的值 public Object get(Object obj) //將指定對(duì)象obj中該字段的值設(shè)為value public void set(Object obj, Object value)在get/set方法中,對(duì)于靜態(tài)變量,obj被忽略,可以為null,如果字段值為基本類(lèi)型,get/set會(huì)自動(dòng)在基本類(lèi)型與對(duì)應(yīng)的包裝類(lèi)型間進(jìn)行轉(zhuǎn)換,對(duì)于private字段,直接調(diào)用get/set會(huì)拋出非法訪問(wèn)異常IllegalAccessException,應(yīng)該先調(diào)用setAccessible(true)以關(guān)閉Java的檢查機(jī)制。
看段簡(jiǎn)單的示例代碼:
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)); }代碼比較簡(jiǎn)單,就不贅述了。我們?cè)赥hreadLocal一節(jié)介紹過(guò)利用反射來(lái)清空ThreadLocal,這里重復(fù)下其代碼,含義就比較清楚了:
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() //返回字段的類(lèi)型 public Class<?> getType() //以基本類(lèi)型操作字段 public void setBoolean(Object obj, boolean z) public boolean getBoolean(Object obj) public void setDouble(Object obj, double d) public double getDouble(Object obj)//查詢(xún)字段的注解信息 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) public Annotation[] getDeclaredAnnotations()getModifiers返回的是一個(gè)int,可以通過(guò)Modifier類(lèi)的靜態(tài)方法進(jìn)行解讀,比如,假定Student類(lèi)有如下字段:
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關(guān)于注解,我們下節(jié)再詳細(xì)介紹。
方法信息
類(lèi)中定義的靜態(tài)和實(shí)例方法都被稱(chēng)為方法,用類(lèi)Method表示,Class有四個(gè)獲取方法信息的方法:
//返回所有的public方法,包括其父類(lèi)的,如果沒(méi)有方法,返回空數(shù)組 public Method[] getMethods() //返回本類(lèi)聲明的所有方法,包括非public的,但不包括父類(lèi)的 public Method[] getDeclaredMethods() //返回本類(lèi)或父類(lèi)中指定名稱(chēng)和參數(shù)類(lèi)型的public方法,找不到拋出異常NoSuchMethodException public Method getMethod(String name, Class<?>... parameterTypes) //返回本類(lèi)中聲明的指定名稱(chēng)和參數(shù)類(lèi)型的方法,找不到拋出異常NoSuchMethodException public Method getDeclaredMethod(String name, Class<?>... parameterTypes)Method也有很多方法,可以獲取方法的信息,也可以通過(guò)Method調(diào)用對(duì)象的方法,基本方法有:
//獲取方法的名稱(chēng) public String getName() //flag設(shè)為true表示忽略Java的訪問(wèn)檢查機(jī)制,以允許調(diào)用非public的方法 public void setAccessible(boolean flag) //在指定對(duì)象obj上調(diào)用Method代表的方法,傳遞的參數(shù)列表為args public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException對(duì)invoke方法,如果Method為靜態(tài)方法,obj被忽略,可以為null,args可以為null,也可以為一個(gè)空的數(shù)組,方法調(diào)用的返回值被包裝為Object返回,如果實(shí)際方法調(diào)用拋出異常,異常被包裝為InvocationTargetException重新拋出,可以通過(guò)getCause方法得到原異常。
看段簡(jiǎn)單的示例代碼:
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還有很多方法,可以獲取方法的修飾符、參數(shù)、返回值、注解等信息,比如:
//獲取方法的修飾符,返回值可通過(guò)Modifier類(lèi)進(jìn)行解讀 public int getModifiers() //獲取方法的參數(shù)類(lèi)型 public Class<?>[] getParameterTypes() //獲取方法的返回值類(lèi)型 public Class<?> getReturnType() //獲取方法聲明拋出的異常類(lèi)型 public Class<?>[] getExceptionTypes() //獲取注解信息 public Annotation[] getDeclaredAnnotations() public <T extends Annotation> T getAnnotation(Class<T> annotationClass) //獲取方法參數(shù)的注解信息 public Annotation[][] getParameterAnnotations()創(chuàng)建對(duì)象和構(gòu)造方法
Class有一個(gè)方法,可以用它來(lái)創(chuàng)建對(duì)象:
public T newInstance() throws InstantiationException, IllegalAccessException它會(huì)調(diào)用類(lèi)的默認(rèn)構(gòu)造方法(即無(wú)參public構(gòu)造方法),如果類(lèi)沒(méi)有該構(gòu)造方法,會(huì)拋出異常InstantiationException??磦€(gè)簡(jiǎn)單示例:
Map<String,Integer> map = HashMap.class.newInstance(); map.put("hello", 123);很多利用反射的庫(kù)和框架都默認(rèn)假定類(lèi)有無(wú)參public構(gòu)造方法,所以當(dāng)類(lèi)利用這些庫(kù)和框架時(shí)要記住提供一個(gè)。
newInstance只能使用默認(rèn)構(gòu)造方法,Class還有一些方法,可以獲取所有的構(gòu)造方法:
//獲取所有的public構(gòu)造方法,返回值可能為長(zhǎng)度為0的空數(shù)組 public Constructor<?>[] getConstructors() //獲取所有的構(gòu)造方法,包括非public的 public Constructor<?>[] getDeclaredConstructors() //獲取指定參數(shù)類(lèi)型的public構(gòu)造方法,沒(méi)找到拋出異常NoSuchMethodException public Constructor<T> getConstructor(Class<?>... parameterTypes) //獲取指定參數(shù)類(lèi)型的構(gòu)造方法,包括非public的,沒(méi)找到拋出異常NoSuchMethodException public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)類(lèi)Constructor表示構(gòu)造方法,通過(guò)它可以創(chuàng)建對(duì)象,方法為:
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);除了創(chuàng)建對(duì)象,Constructor還有很多方法,可以獲取關(guān)于構(gòu)造方法的很多信息,比如:
//獲取參數(shù)的類(lèi)型信息 public Class<?>[] getParameterTypes() //構(gòu)造方法的修飾符,返回值可通過(guò)Modifier類(lèi)進(jìn)行解讀 public int getModifiers() //構(gòu)造方法的注解信息 public Annotation[] getDeclaredAnnotations() public <T extends Annotation> T getAnnotation(Class<T> annotationClass) //構(gòu)造方法中參數(shù)的注解信息 public Annotation[][] getParameterAnnotations()類(lèi)型檢查和轉(zhuǎn)換
我們?cè)?6節(jié)介紹過(guò)instanceof關(guān)鍵字,它可以用來(lái)判斷變量指向的實(shí)際對(duì)象類(lèi)型,instanceof后面的類(lèi)型是在代碼中確定的,如果要檢查的類(lèi)型是動(dòng)態(tài)的,可以使用Class類(lèi)的如下方法:
public native boolean isInstance(Object obj)也就是說(shuō),如下代碼:
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"); }除了判斷類(lèi)型,在程序中也往往需要進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換,比如:
List list = .. if(list instanceof ArrayList){ArrayList arrList = (ArrayList)list; }在這段代碼中,強(qiáng)制轉(zhuǎn)換到的類(lèi)型是在寫(xiě)代碼時(shí)就知道的,如果是動(dòng)態(tài)的,可以使用Class的如下方法:
public T cast(Object obj)比如:
public static <T> T toType(Object obj, Class<T> cls){return cls.cast(obj); }isInstance/cast描述的都是對(duì)象和類(lèi)之間的關(guān)系,Class還有一個(gè)方法,可以判斷Class之間的關(guān)系:
// 檢查參數(shù)類(lèi)型cls能否賦給當(dāng)前Class類(lèi)型的變量 public native boolean isAssignableFrom(Class<?> cls);比如,如下表達(dá)式的結(jié)果都為true:
Object.class.isAssignableFrom(String.class) String.class.isAssignableFrom(String.class) List.class.isAssignableFrom(ArrayList.class)Class的類(lèi)型信息
Class代表的類(lèi)型既可以是普通的類(lèi)、也可以是內(nèi)部類(lèi),還可以是基本類(lèi)型、數(shù)組等,對(duì)于一個(gè)給定的Class對(duì)象,它到底是什么類(lèi)型呢?可以通過(guò)以下方法進(jìn)行檢查:
//是否是數(shù)組 public native boolean isArray(); //是否是基本類(lèi)型 public native boolean isPrimitive(); //是否是接口 public native boolean isInterface(); //是否是枚舉 public boolean isEnum() //是否是注解 public boolean isAnnotation() //是否是匿名內(nèi)部類(lèi) public boolean isAnonymousClass() //是否是成員類(lèi) public boolean isMemberClass() //是否是本地類(lèi) public boolean isLocalClass()需要說(shuō)明下匿名內(nèi)部類(lèi)、成員類(lèi)與本地類(lèi)的區(qū)別,本地類(lèi)是指在方法內(nèi)部定義的非匿名內(nèi)部類(lèi),比如,如下代碼:
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方法內(nèi)部,就是一個(gè)本地類(lèi),r的對(duì)象所屬的類(lèi)是一個(gè)匿名類(lèi),但不是本地類(lèi)。
成員類(lèi)也是內(nèi)部類(lèi),定義在類(lèi)內(nèi)部、方法外部,它不是匿名類(lèi),也不是本地類(lèi)。
類(lèi)的聲明信息
Class還有很多方法,可以獲取類(lèi)的聲明信息,如修飾符、父類(lèi)、實(shí)現(xiàn)的接口、注解等,如下所示:
//獲取修飾符,返回值可通過(guò)Modifier類(lèi)進(jìn)行解讀 public native int getModifiers() //獲取父類(lèi),如果為Object,父類(lèi)為null public native Class<? super T> getSuperclass() //對(duì)于類(lèi),為自己聲明實(shí)現(xiàn)的所有接口,對(duì)于接口,為直接擴(kuò)展的接口,不包括通過(guò)父類(lèi)間接繼承來(lái)的 public native Class<?>[] getInterfaces(); //自己聲明的注解 public Annotation[] getDeclaredAnnotations() //所有的注解,包括繼承得到的 public Annotation[] getAnnotations() //獲取或檢查指定類(lèi)型的注解,包括繼承得到的 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)內(nèi)部類(lèi)
關(guān)于內(nèi)部類(lèi),Class有一些專(zhuān)門(mén)的方法,比如:
//獲取所有的public的內(nèi)部類(lèi)和接口,包括從父類(lèi)繼承得到的 public Class<?>[] getClasses() //獲取自己聲明的所有的內(nèi)部類(lèi)和接口 public Class<?>[] getDeclaredClasses() //如果當(dāng)前Class為內(nèi)部類(lèi),獲取聲明該類(lèi)的最外部的Class對(duì)象 public Class<?> getDeclaringClass() //如果當(dāng)前Class為內(nèi)部類(lèi),獲取直接包含該類(lèi)的類(lèi) public Class<?> getEnclosingClass() //如果當(dāng)前Class為本地類(lèi)或匿名內(nèi)部類(lèi),返回包含它的方法 public Method getEnclosingMethod()類(lèi)的加載
Class有兩個(gè)靜態(tài)方法,可以根據(jù)類(lèi)名加載類(lèi):
public static Class<?> forName(String className) public static Class<?> forName(String name, boolean initialize, ClassLoader loader)ClassLoader表示類(lèi)加載器,后面章節(jié)我們會(huì)進(jìn)一步介紹,initialize表示加載后,是否執(zhí)行類(lèi)的初始化代碼(如static語(yǔ)句塊)。第一個(gè)方法中沒(méi)有傳這些參數(shù),相當(dāng)于調(diào)用:
Class.forName(className, true, currentLoader)currentLoader表示加載當(dāng)前類(lèi)的ClassLoader。
這里className與Class.getName的返回值是一致的,比如,對(duì)于String數(shù)組:
String name = "[Ljava.lang.String;"; Class cls = Class.forName(name); System.out.println(cls == String[].class);需要注意的是,基本類(lèi)型不支持forName方法,也就是說(shuō),如下寫(xiě)法:
Class.forName("int");會(huì)拋出異常ClassNotFoundException,那如何根據(jù)原始類(lèi)型的字符串構(gòu)造Class對(duì)象呢?可以對(duì)Class.forName進(jìn)行一下包裝,比如:
public static Class<?> forName(String className) throws ClassNotFoundException{if("int".equals(className)){return int.class;}//其他基本類(lèi)型...return Class.forName(className); }反射與數(shù)組
對(duì)于數(shù)組類(lèi)型,有一個(gè)專(zhuān)門(mén)的方法,可以獲取它的元素類(lèi)型:
public native Class<?> getComponentType()比如:
String[] arr = new String[]{}; System.out.println(arr.getClass().getComponentType());輸出為:
class java.lang.Stringjava.lang.reflect包中有一個(gè)針對(duì)數(shù)組的專(zhuān)門(mén)的類(lèi)Array(注意不是java.util中的Arrays),提供了對(duì)于數(shù)組的一些反射支持,以便于統(tǒng)一處理多種類(lèi)型的數(shù)組,主要方法有:
//創(chuàng)建指定元素類(lèi)型、指定長(zhǎng)度的數(shù)組, public static Object newInstance(Class<?> componentType, int length) //創(chuàng)建多維數(shù)組 public static Object newInstance(Class<?> componentType, int... dimensions) //獲取數(shù)組array指定的索引位置index處的值 public static native Object get(Object array, int index) //修改數(shù)組array指定的索引位置index處的值為value public static native void set(Object array, int index, Object value) //返回?cái)?shù)組的長(zhǎng)度 public static native int getLength(Object array)需要注意的是,在Array類(lèi)中,數(shù)組是用Object而非Object[]表示的,這是為什么呢?這是為了方便處理多種類(lèi)型的數(shù)組,int[],String[]都不能與Object[]相互轉(zhuǎn)換,但可以與Object相互轉(zhuǎn)換,比如:
int[] intArr = (int[])Array.newInstance(int.class, 10); String[] strArr = (String[])Array.newInstance(String.class, 10);除了以O(shè)bject類(lèi)型操作數(shù)組元素外,Array也支持以各種基本類(lèi)型操作數(shù)組元素,如:
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)反射與枚舉
枚舉類(lèi)型也有一個(gè)專(zhuān)門(mén)方法,可以獲取所有的枚舉常量:
public T[] getEnumConstants()應(yīng)用示例
介紹了Class的這么多方法,有什么用呢?我們看個(gè)簡(jiǎn)單的示例,利用反射實(shí)現(xiàn)一個(gè)簡(jiǎn)單的通用序列化/反序列化類(lèi)SimpleMapper,它提供兩個(gè)靜態(tài)方法:
public static String toString(Object obj) public static Object fromString(String str)toString將對(duì)象obj轉(zhuǎn)換為字符串,fromString將字符串轉(zhuǎn)換為對(duì)象。為簡(jiǎn)單起見(jiàn),我們只支持最簡(jiǎn)單的類(lèi),即有默認(rèn)構(gòu)造方法,成員類(lèi)型只有基本類(lèi)型、包裝類(lèi)或String。另外,序列化的格式也很簡(jiǎn)單,第一行為類(lèi)的名稱(chēng),后面每行表示一個(gè)字段,用字符'='分隔,表示字段名稱(chēng)和字符串形式的值。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);} }代碼先調(diào)用toString方法將對(duì)象轉(zhuǎn)換為了String,然后調(diào)用fromString方法將字符串轉(zhuǎn)換為了Student,新對(duì)象的值與原對(duì)象是一樣的,輸出如下所示:
Student [name=張三, age=18, score=89.0]我們來(lái)看SimpleMapper的示例實(shí)現(xiàn)(主要用于演示原理,在生產(chǎn)中謹(jǐn)慎使用),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);} }它調(diào)用了setFieldValue方法對(duì)字段設(shè)置值,其代碼為:
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根據(jù)字段的類(lèi)型,將字符串形式的值轉(zhuǎn)換為了對(duì)應(yīng)類(lèi)型的值,對(duì)于基本類(lèi)型和String以外的類(lèi)型,它假定該類(lèi)型有一個(gè)以String類(lèi)型為參數(shù)的構(gòu)造方法。
反射與泛型
在介紹泛型的時(shí)候,我們提到,泛型參數(shù)在運(yùn)行時(shí)會(huì)被擦除,這里,我們需要補(bǔ)充一下,在類(lèi)信息Class中依然有關(guān)于泛型的一些信息,可以通過(guò)反射得到,泛型涉及到一些更多的方法和類(lèi),上面的介紹中進(jìn)行了忽略,這里簡(jiǎn)要補(bǔ)充下。
Class有如下方法,可以獲取類(lèi)的泛型參數(shù)信息:
public TypeVariable<Class<T>>[] getTypeParameters()Field有如下方法:
public Type getGenericType()Method有如下方法:
public Type getGenericReturnType() public Type[] getGenericParameterTypes() public Type[] getGenericExceptionTypes()Constructor有如下方法:
public Type[] getGenericParameterTypes()Type是一個(gè)接口,Class實(shí)現(xiàn)了Type,Type的其他子接口還有:
- TypeVariable:類(lèi)型參數(shù),可以有上界,比如:T extends Number
- ParameterizedType:參數(shù)化的類(lèi)型,有原始類(lèi)型和具體的類(lèi)型參數(shù),比如:List<String>?
- WildcardType:通配符類(lèi)型,比如:?, ? extends Number, ? super Integer
我們看一個(gè)簡(jiǎn)單的示例:
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;// 類(lèi)的類(lèi)型參數(shù)for (TypeVariable t : cls.getTypeParameters()) {System.out.println(t.getName() + " extends " + Arrays.toString(t.getBounds()));}// 字段 - 泛型類(lèi)型Field fu = cls.getDeclaredField("u");System.out.println(fu.getGenericType());// 字段 - 參數(shù)化的類(lèi)型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()));}// 方法的泛型參數(shù)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>代碼比較簡(jiǎn)單,我們就不贅述了。
慎用反射
反射雖然是靈活的,但一般情況下,并不是我們優(yōu)先建議的,主要原因是:
- 反射更容易出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,使用顯式的類(lèi)和接口,編譯器能幫我們做類(lèi)型檢查,減少錯(cuò)誤,但使用反射,類(lèi)型是運(yùn)行時(shí)才知道的,編譯器無(wú)能為力
- 反射的性能要低一些,在訪問(wèn)字段、調(diào)用方法前,反射先要查找對(duì)應(yīng)的Field/Method,性能要慢一些
簡(jiǎn)單的說(shuō),如果能用接口實(shí)現(xiàn)同樣的靈活性,就不要使用反射。
小結(jié)
本節(jié)介紹了Java中反射相關(guān)的主要類(lèi)和方法,通過(guò)入口類(lèi)Class,可以訪問(wèn)類(lèi)的各種信息,如字段、方法、構(gòu)造方法、父類(lèi)、接口、泛型信息等,也可以創(chuàng)建和操作對(duì)象,調(diào)用方法等,利用這些方法,可以編寫(xiě)通用的、動(dòng)態(tài)靈活的程序,本節(jié)演示了一個(gè)簡(jiǎn)單的通用序列化/反序列化類(lèi)SimpleMapper。反射雖然是靈活通用的,但它更容易出現(xiàn)運(yùn)行時(shí)錯(cuò)誤,所以,能用接口代替的時(shí)候,應(yīng)該盡量使用接口。
本節(jié)介紹的很多類(lèi)如Class/Field/Method/Constructor都可以有注解,注解到底是什么呢?
(與其他章節(jié)一樣,本節(jié)所有代碼位于 https://github.com/swiftma/program-logic,位于包shuo.laoma.dynamic.c84下)
----------------
未完待續(xù),查看最新文章,敬請(qǐng)關(guān)注微信公眾號(hào)“老馬說(shuō)編程”(掃描下方二維碼),從入門(mén)到高級(jí),深入淺出,老馬和你一起探索Java編程及計(jì)算機(jī)技術(shù)的本質(zhì)。用心原創(chuàng),保留所有版權(quán)。
總結(jié)
以上是生活随笔為你收集整理的Java编程的逻辑 (84) - 反射的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: netty简单笔记
- 下一篇: Java学习笔记3——集合框架