摘要:反射使用類對象提供的基本元數據,能從類對象中找出方法或字段的名稱,然后獲取表示方法或字段的對象。常見的反射手段有反射和反射。以之前的反射為例其中指定了方法的返回類型,其實不止如此。
Java反射機制主要提供了以下功能:
在運行時判斷任意一個對象所屬的類
在運行時構造任意一個類的對象
在運行時判斷任意一個類所具有的成員變量和方法
在運行時調用任意一個對象的方法
生成動態代理
很多框架都用到了反射機制,包括大名鼎鼎的Spring。因此,了解反射也可以說是為之后學習框架源碼而打下堅實的基礎。
即便編譯時不知道類型和方法名稱,也能使用反射。反射使用類對象提供的基本元數據,能從類對象中找出方法或字段的名稱,然后獲取表示方法或字段的對象。
在Java中,靜態成員和普通數據類型不是對象,其他皆是。
那么問題來了,類是誰的對象?
是java.lang.Class的實例對象。
Class.forName(ClassName)//可以動態加載類——也就是運行時加載
(使用 Class::newInstance() 或另一個構造方法)創建實例時也能讓實例具有反射功能。如果有一個能反射的對象和一個 Method 對象,我們就能在之前類型未知的對象上調用任何方法。
反射出來的對象信息是幾乎未知的,所以反射也并不是那么的好用。
什么時候用反射很多,也許是多數 Java 框架都會適度使用反射。如果編寫的架構足夠靈活,在運行時之前都不知道要處理什么代碼,那么通常都需要使用反射。例如,插入式架構、調試器、代碼瀏覽器和 REPL 類環境往往都會在反射的基礎上實現。
反射在測試中也有廣泛應用,例如,JUnit 和 TestNG 庫都用到了反射,而且創建模擬對象也要使用反射。如果你用過任何一個 Java 框架,即便沒有意識到,也幾乎可以確定,你使用的是具有反射功能的代碼。
常見的反射手段有JDK反射和cglib反射。
在自己的代碼中使用反射 API 時一定要知道,獲取到的對象幾乎所有信息都未知,因此處理起來可能很麻煩。
只要知道動態加載的類的一些靜態信息(例如,加載的類實現一個已知的接口),與這個類交互的過程就能大大簡化,減輕反射操作的負擔。
使用反射時有個常見的誤區:試圖創建能適用于所有場合的反射框架。正確的做法是,只處理當前領域立即就能解決的問題。
如何使用反射使用反射的第一步就是獲取Class對象,Class對象里存儲了很多關鍵信息——畢竟這是用來描述類的class。
我們可以這樣來獲取Class信息:
Class> clz = Class.forName(obj.getClazz()); //通過class生成相應的實例 Object newObj = clz.newInstance
從Java1.5開始,Class類就支持泛型化了。比如:String.class就是ClassMethod對象類型。
在反射最常用的API就是Method了。
類對象中包含該類中每個方法的 Method 對象。這些 Method 對象在類加載之后惰性創建,所以在 IDE 的調試器中不會立即出現。
Method對象中保存的方法和元數據:
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[] { }; //其實這個參數沒有也沒關系,因為hashCode方法不需要參數 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(); }
如果要調用非公開方法,必須使用 getDeclaredMethod() 方法才能獲取非公開方法的引用,而且還要使用 setAccessible() 方法覆蓋 Java 的訪問控制子系統,然后才能執行:
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 往往是處理動態加載代碼的唯一方式,不過 API 中有些讓人頭疼的地方,處理起來稍微有點困難:
大量使用 Object[] 表示調用參數和其他實例;
大量使用 Class[] 表示類型;
同名方法可以重載,所以需要維護一個類型組成的數組,區分不同的方法;
不能很好地表示基本類型——需要手動打包和拆包。
void 就是個明顯的問題——雖然有 void.class,但沒堅持用下去。Java 甚至不知道 void 是不是一種類型,而且反射 API 中的某些方法使用 null 代替 void。
這很難處理,而且容易出錯,尤其是稍微有點冗長的數組句法,更容易出錯。
動態代理Java反射的API中還提供了動態代理。動態代理是實現了一些接口的類(擴展 java.lang.reflect.Proxy 類)。這些類在運行時動態創建,而且會把所有調用都轉交給 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();
代理可以用作測試的替身對象(尤其是測試使用模擬方式實現的對象)。
代理的另一個作用是提供接口的部分實現,或者修飾或控制委托對象的某些方面:
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中提供了方法句柄,比起“傳統”的反射機制。更為好用,而且性能更好。
以之前的反射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);指定了方法的返回類型,其實不止如此。methodType還可以填入其函數參數
通過MethodHandles.Lookup可以獲得當前執行方法的上下文對象,在這個對象上可以調用幾個方法(方法名都以 find 開頭),查找需要的方法,包括findVirtual()、findConstructor()` 和 findStatic()
反射 API 和方法句柄 API 之間一個重大的區別是處理訪問控制的方式。Lookup 對象只會返回在創建這個對象的上下文中可以訪問的方法——沒有任何方式能破壞這個規則(不像反射 API 可以使用 setAccessible() 方法調整訪問控制)
通過 Lookup 對象可以為任何能訪問的方法生成方法句柄,還能訪問方法無法訪問的字段。在 Lookup 對象上調用 findGetter() 和 findSetter() 方法,分別可以生成讀取字段和更新字段的方法句柄。
之后通過MethodHandles.Lookup.findVirtual()獲得了方法句柄(MethodHandle)
方法句柄表示調用方法的能力。方法句柄對象是強類型的,會盡量保證類型安全。方法句柄都是 java.lang.invoke.MethodHandle 類的子類實例,JVM 會使用特殊的方式處理這個類。
一般來說,invoke() 方法會調用 asType() 方法轉換參數。轉換的規則如下:
如果需要,打包基本類型的參數。
如果需要,拆包打包好的基本類型參數。
如果需要,放大轉換基本類型的參數。
會把 void 返回類型修改為 0 或 null,具體是哪個取決于期待的返回值是基本類型還是引用類型。
不管靜態類型是什么,都能傳入 null。
小結方法句柄提供的動態編程功能和反射一樣,但處理方式更清晰明了。而且,方法句柄能在 JVM 的低層執行模型中很好地運轉,因此,性能比反射好得多。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66841.html
摘要:介紹微信風格的,與客戶端體驗一致,這個自己去微信上看吧,略。微信調試一件套,網頁授權模擬集成代理遠程調試。這些在微信開發者中心有介紹,略。年微信開發經驗的人,終于又成為了零年開發經驗的人,重新走上了踩坑之路。 showImg(https://segmentfault.com/img/bVtEd1);活動地址:http://fequan.com/2016/ 注意:英文不好,小記也帶有自己...
摘要:等待其安裝完成后關閉程序,重新啟動,點開菜單可見項,說明插件管理包已安裝成功。在出現的懸浮對話框中輸入然后點選下面的插件,就會自動開始安裝,請耐心等待。【注:以下內容參考https://blog.csdn.net/stilling2006/article/details/54376743】 一、認識Sublime text 1、一款跨平臺代碼編輯器,在Linux、OSX和Windows下均可...
摘要:抽象類和接口小記抽象類和接口實現了的多態多態是面向對象程序語言的核心在項目開發過程中其實很少使用抽象類接口用得比較多今天小記一下抽象類和接口的區別抽象類抽象類不能被實例化抽象類可以繼承可以定義變量可以定義構造方法抽象方法的要顯式的寫出來其子 Java抽象類和接口小記 Java抽象類和接口實現了java的多態.多態是面向對象程序語言的核心,在項目開發過程中,其實很少使用抽象類,接口用得比...
摘要:要注意的是,成員內部類不能含有的變量和方法。匿名內部類是唯一一種沒有構造器的類。靜態嵌套類又叫靜態局部類嵌套內部類,就是修飾為的內部類。以上是對內部類的一些學習和總結,紕漏之處希望各位小伙伴友情指點,共同進步。 內部類(inner class)是定義在另一個類中的類,類名不需要和文件夾相同。但為什么要使用內部類呢?其主要原因有以下三點: 1.內部類方法可以訪問該類定義所在的作用域中的...
摘要:簡單地說程序就是數據和方法計算機能做的就是計算這個數據可以是字符串各種類型的數值整數小數等類內的屬性根本上是還是的基本數據類型布爾類型的東東為了更加快速地寫出代碼現在的語言都是高層次的抽象即所謂的高級編程語言了高級編程語言中的一些特性如訪問 簡單地說, 程序就是數據和方法, 計算機能做的就是計算, 這個數據可以是: 1.字符串, 2.各種類型的數值(整數, 小數等), 3.Java類內...
閱讀 1734·2021-10-18 13:30
閱讀 2608·2021-10-09 10:02
閱讀 2965·2021-09-28 09:35
閱讀 2091·2019-08-26 13:39
閱讀 3522·2019-08-26 13:36
閱讀 1950·2019-08-26 11:46
閱讀 1135·2019-08-23 14:56
閱讀 1694·2019-08-23 10:38