摘要:反射使用類對象提供的基本元數(shù)據(jù),能從類對象中找出方法或字段的名稱,然后獲取表示方法或字段的對象。常見的反射手段有反射和反射。以之前的反射為例其中指定了方法的返回類型,其實(shí)不止如此。
Java反射機(jī)制主要提供了以下功能:
在運(yùn)行時(shí)判斷任意一個(gè)對象所屬的類
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對象
在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法
在運(yùn)行時(shí)調(diào)用任意一個(gè)對象的方法
生成動態(tài)代理
很多框架都用到了反射機(jī)制,包括大名鼎鼎的Spring。因此,了解反射也可以說是為之后學(xué)習(xí)框架源碼而打下堅(jiān)實(shí)的基礎(chǔ)。
即便編譯時(shí)不知道類型和方法名稱,也能使用反射。反射使用類對象提供的基本元數(shù)據(jù),能從類對象中找出方法或字段的名稱,然后獲取表示方法或字段的對象。
在Java中,靜態(tài)成員和普通數(shù)據(jù)類型不是對象,其他皆是。
那么問題來了,類是誰的對象?
是java.lang.Class的實(shí)例對象。
Class.forName(ClassName)//可以動態(tài)加載類——也就是運(yùn)行時(shí)加載
(使用 Class::newInstance() 或另一個(gè)構(gòu)造方法)創(chuàng)建實(shí)例時(shí)也能讓實(shí)例具有反射功能。如果有一個(gè)能反射的對象和一個(gè) Method 對象,我們就能在之前類型未知的對象上調(diào)用任何方法。
反射出來的對象信息是幾乎未知的,所以反射也并不是那么的好用。
什么時(shí)候用反射很多,也許是多數(shù) Java 框架都會適度使用反射。如果編寫的架構(gòu)足夠靈活,在運(yùn)行時(shí)之前都不知道要處理什么代碼,那么通常都需要使用反射。例如,插入式架構(gòu)、調(diào)試器、代碼瀏覽器和 REPL 類環(huán)境往往都會在反射的基礎(chǔ)上實(shí)現(xiàn)。
反射在測試中也有廣泛應(yīng)用,例如,JUnit 和 TestNG 庫都用到了反射,而且創(chuàng)建模擬對象也要使用反射。如果你用過任何一個(gè) Java 框架,即便沒有意識到,也幾乎可以確定,你使用的是具有反射功能的代碼。
常見的反射手段有JDK反射和cglib反射。
在自己的代碼中使用反射 API 時(shí)一定要知道,獲取到的對象幾乎所有信息都未知,因此處理起來可能很麻煩。
只要知道動態(tài)加載的類的一些靜態(tài)信息(例如,加載的類實(shí)現(xiàn)一個(gè)已知的接口),與這個(gè)類交互的過程就能大大簡化,減輕反射操作的負(fù)擔(dān)。
使用反射時(shí)有個(gè)常見的誤區(qū):試圖創(chuàng)建能適用于所有場合的反射框架。正確的做法是,只處理當(dāng)前領(lǐng)域立即就能解決的問題。
如何使用反射使用反射的第一步就是獲取Class對象,Class對象里存儲了很多關(guān)鍵信息——畢竟這是用來描述類的class。
我們可以這樣來獲取Class信息:
Class> clz = Class.forName(obj.getClazz()); //通過class生成相應(yīng)的實(shí)例 Object newObj = clz.newInstance
從Java1.5開始,Class類就支持泛型化了。比如:String.class就是ClassMethod對象類型。
在反射最常用的API就是Method了。
類對象中包含該類中每個(gè)方法的 Method 對象。這些 Method 對象在類加載之后惰性創(chuàng)建,所以在 IDE 的調(diào)試器中不會立即出現(xiàn)。
Method對象中保存的方法和元數(shù)據(jù):
private Class> clazz; private int slot; // This is guaranteed to be interned by the VM in the 1.4 // reflection implementation private String name; private Class> returnType; private Class>[] parameterTypes; private Class>[] exceptionTypes private int modifiers; // Generics and annotations support private transient String signature; // Generic info repository; lazily initialized private transient MethodRepository genericInfo; private byte[] annotations; private byte[] parameterAnnotations; private byte[] annotationDefault; private volatile MethodAccessor methodAccessor;
我們可以通過getMethod獲得對象的方法:
Object rcvr = "str"; try { Class>[] argTypes = new Class[] { }; //其實(shí)這個(gè)參數(shù)沒有也沒關(guān)系,因?yàn)閔ashCode方法不需要參數(shù) Object[] args = null; Method hasMeth = rcvr.getClass().getMethod("hashCode", argTypes); Object ret = hasMeth.invoke(rcvr,args); System.out.println(ret); } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException x) { x.printStackTrace(); }
如果要調(diào)用非公開方法,必須使用 getDeclaredMethod() 方法才能獲取非公開方法的引用,而且還要使用 setAccessible() 方法覆蓋 Java 的訪問控制子系統(tǒng),然后才能執(zhí)行:
public class MyCache { private void flush() { // 清除緩存…… } } Class> clz = MyCache.class; try { Object rcvr = clz.newInstance(); Class>[] argTypes = new Class[]{}; Object[] args = null; Method meth = clz.getDeclaredMethod("flush", argTypes); meth.setAccessible(true); meth.invoke(rcvr, args); } catch (IllegalArgumentException | NoSuchMethodException | InstantiationException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException x) { x.printStackTrace(); }反射的問題
Java 的反射 API 往往是處理動態(tài)加載代碼的唯一方式,不過 API 中有些讓人頭疼的地方,處理起來稍微有點(diǎn)困難:
大量使用 Object[] 表示調(diào)用參數(shù)和其他實(shí)例;
大量使用 Class[] 表示類型;
同名方法可以重載,所以需要維護(hù)一個(gè)類型組成的數(shù)組,區(qū)分不同的方法;
不能很好地表示基本類型——需要手動打包和拆包。
void 就是個(gè)明顯的問題——雖然有 void.class,但沒堅(jiān)持用下去。Java 甚至不知道 void 是不是一種類型,而且反射 API 中的某些方法使用 null 代替 void。
這很難處理,而且容易出錯,尤其是稍微有點(diǎn)冗長的數(shù)組句法,更容易出錯。
動態(tài)代理Java反射的API中還提供了動態(tài)代理。動態(tài)代理是實(shí)現(xiàn)了一些接口的類(擴(kuò)展 java.lang.reflect.Proxy 類)。這些類在運(yùn)行時(shí)動態(tài)創(chuàng)建,而且會把所有調(diào)用都轉(zhuǎn)交給 InvocationHandler 對象處理:
InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws javaThrowable { String name = method.getName(); System.out.println("Called as: "+ name); switch (name) { case "isOpen": return false; case "close": return null; } return null; } }; Channel c = (Channel) Proxy.newProxyInstance(Channel.class.getClassLoader(), new Class[] { Channel.class }, h); c.isOpen(); c.close();
代理可以用作測試的替身對象(尤其是測試使用模擬方式實(shí)現(xiàn)的對象)。
代理的另一個(gè)作用是提供接口的部分實(shí)現(xiàn),或者修飾或控制委托對象的某些方面:
public class RememberingList implements InvocationHandler { private final ListJava7中的方法句柄proxied = new ArrayList<>(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); switch (name) { case "clear": return null; case "remove": case "removeAll": return false; } return method.invoke(proxied, args); } } RememberingList hList = new RememberingList(); List l = (List ) Proxy.newProxyInstance(List.class.getClassLoader(), new Class[] { List.class }, hList); l.add("cat"); l.add("bunny"); l.clear(); System.out.println(l);
Java7中提供了方法句柄,比起“傳統(tǒng)”的反射機(jī)制。更為好用,而且性能更好。
以之前的反射hashCode為例
Object rcvr = "str"; try { MethodType mt = MethodType.methodType(int.class); MethodHandles.Lookup l = MethodHandles.lookup(); MethodHandle hashMeth = l.findVirtual(rcvr.getClass(), "hashCode", mt); int result; try { result = (int) hashMeth.invoke(rcvr); System.out.println(result); } catch (Throwable t) { t.printStackTrace(); } } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); }
其中MethodType.methodType(int.class);指定了方法的返回類型,其實(shí)不止如此。methodType還可以填入其函數(shù)參數(shù)
通過MethodHandles.Lookup可以獲得當(dāng)前執(zhí)行方法的上下文對象,在這個(gè)對象上可以調(diào)用幾個(gè)方法(方法名都以 find 開頭),查找需要的方法,包括findVirtual()、findConstructor()` 和 findStatic()
反射 API 和方法句柄 API 之間一個(gè)重大的區(qū)別是處理訪問控制的方式。Lookup 對象只會返回在創(chuàng)建這個(gè)對象的上下文中可以訪問的方法——沒有任何方式能破壞這個(gè)規(guī)則(不像反射 API 可以使用 setAccessible() 方法調(diào)整訪問控制)
通過 Lookup 對象可以為任何能訪問的方法生成方法句柄,還能訪問方法無法訪問的字段。在 Lookup 對象上調(diào)用 findGetter() 和 findSetter() 方法,分別可以生成讀取字段和更新字段的方法句柄。
之后通過MethodHandles.Lookup.findVirtual()獲得了方法句柄(MethodHandle)
方法句柄表示調(diào)用方法的能力。方法句柄對象是強(qiáng)類型的,會盡量保證類型安全。方法句柄都是 java.lang.invoke.MethodHandle 類的子類實(shí)例,JVM 會使用特殊的方式處理這個(gè)類。
一般來說,invoke() 方法會調(diào)用 asType() 方法轉(zhuǎn)換參數(shù)。轉(zhuǎn)換的規(guī)則如下:
如果需要,打包基本類型的參數(shù)。
如果需要,拆包打包好的基本類型參數(shù)。
如果需要,放大轉(zhuǎn)換基本類型的參數(shù)。
會把 void 返回類型修改為 0 或 null,具體是哪個(gè)取決于期待的返回值是基本類型還是引用類型。
不管靜態(tài)類型是什么,都能傳入 null。
小結(jié)方法句柄提供的動態(tài)編程功能和反射一樣,但處理方式更清晰明了。而且,方法句柄能在 JVM 的低層執(zhí)行模型中很好地運(yùn)轉(zhuǎn),因此,性能比反射好得多。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/66841.html
摘要:介紹微信風(fēng)格的,與客戶端體驗(yàn)一致,這個(gè)自己去微信上看吧,略。微信調(diào)試一件套,網(wǎng)頁授權(quán)模擬集成代理遠(yuǎn)程調(diào)試。這些在微信開發(fā)者中心有介紹,略。年微信開發(fā)經(jīng)驗(yàn)的人,終于又成為了零年開發(fā)經(jīng)驗(yàn)的人,重新走上了踩坑之路。 showImg(https://segmentfault.com/img/bVtEd1);活動地址:http://fequan.com/2016/ 注意:英文不好,小記也帶有自己...
摘要:等待其安裝完成后關(guān)閉程序,重新啟動,點(diǎn)開菜單可見項(xiàng),說明插件管理包已安裝成功。在出現(xiàn)的懸浮對話框中輸入然后點(diǎn)選下面的插件,就會自動開始安裝,請耐心等待。【注:以下內(nèi)容參考https://blog.csdn.net/stilling2006/article/details/54376743】 一、認(rèn)識Sublime text 1、一款跨平臺代碼編輯器,在Linux、OSX和Windows下均可...
摘要:抽象類和接口小記抽象類和接口實(shí)現(xiàn)了的多態(tài)多態(tài)是面向?qū)ο蟪绦蛘Z言的核心在項(xiàng)目開發(fā)過程中其實(shí)很少使用抽象類接口用得比較多今天小記一下抽象類和接口的區(qū)別抽象類抽象類不能被實(shí)例化抽象類可以繼承可以定義變量可以定義構(gòu)造方法抽象方法的要顯式的寫出來其子 Java抽象類和接口小記 Java抽象類和接口實(shí)現(xiàn)了java的多態(tài).多態(tài)是面向?qū)ο蟪绦蛘Z言的核心,在項(xiàng)目開發(fā)過程中,其實(shí)很少使用抽象類,接口用得比...
摘要:要注意的是,成員內(nèi)部類不能含有的變量和方法。匿名內(nèi)部類是唯一一種沒有構(gòu)造器的類。靜態(tài)嵌套類又叫靜態(tài)局部類嵌套內(nèi)部類,就是修飾為的內(nèi)部類。以上是對內(nèi)部類的一些學(xué)習(xí)和總結(jié),紕漏之處希望各位小伙伴友情指點(diǎn),共同進(jìn)步。 內(nèi)部類(inner class)是定義在另一個(gè)類中的類,類名不需要和文件夾相同。但為什么要使用內(nèi)部類呢?其主要原因有以下三點(diǎn): 1.內(nèi)部類方法可以訪問該類定義所在的作用域中的...
摘要:簡單地說程序就是數(shù)據(jù)和方法計(jì)算機(jī)能做的就是計(jì)算這個(gè)數(shù)據(jù)可以是字符串各種類型的數(shù)值整數(shù)小數(shù)等類內(nèi)的屬性根本上是還是的基本數(shù)據(jù)類型布爾類型的東東為了更加快速地寫出代碼現(xiàn)在的語言都是高層次的抽象即所謂的高級編程語言了高級編程語言中的一些特性如訪問 簡單地說, 程序就是數(shù)據(jù)和方法, 計(jì)算機(jī)能做的就是計(jì)算, 這個(gè)數(shù)據(jù)可以是: 1.字符串, 2.各種類型的數(shù)值(整數(shù), 小數(shù)等), 3.Java類內(nèi)...
閱讀 1826·2021-10-18 13:30
閱讀 2701·2021-10-09 10:02
閱讀 3050·2021-09-28 09:35
閱讀 2147·2019-08-26 13:39
閱讀 3589·2019-08-26 13:36
閱讀 2011·2019-08-26 11:46
閱讀 1194·2019-08-23 14:56
閱讀 1778·2019-08-23 10:38