简明扼要的反射入门教程
反射
反射作為RTTI語言(比如Java)的基礎之一被很多人所熟知,但是有些同學對反射本身還是懵懵懂懂的,不是很清楚它到底有什么用。今天這節(jié)課我們就對反射本身來一個通體的認知。
定義
反射所在的包為:java.lang.reflect
它的英文版定義是:Reflection allows programmatic access to information about the fields, methods and constructors of loaded classes。the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
By default, a reflected object is not accessible.
Setting the accessible flag in a reflected object permits sophisticated applications with sufficient privilege, such as Java Object Serialization or other persistence mechanisms, to manipulate objects in a manner that would normally be prohibited.
Java反射主要是指程序可以訪問或者修改它本身狀態(tài)或行為的一種能力,是Java被視為動態(tài)(或準動態(tài))語言的一個關鍵性質(zhì)。這個機制允許程序在運行時通過Reflection APIs取得任何一個已知名稱的class的內(nèi)部信息,包括其modifiers(諸如public, static 等)、superclass(例如Object)、實現(xiàn)之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于運行時改變fields內(nèi)容或喚起methods。
PS: 因為反射機制與Class類聯(lián)系緊密,所以在學習反射之前需要先了解Class類是什么。
Android文檔中的反射定義:https://developer.android.google.cn/reference/java/lang/reflect/package-summary.html
作用
動態(tài)的訪問、修改類的成員,可以達到使用常規(guī)手段做不到的目的。
最常見的例子:一個類有一個私有的成員屬性,無法通過正常的手段(比如Get方法)獲取這個屬性的值,那么就需要通過反射來獲得它的值,
反射多用于框架和組件,通過反射可以寫出復用性高的通用程序。
比如我們所熟知的Android中的Activity就是通過反射實例化生成的。
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {return (Activity)cl.loadClass(className).newInstance();}最后這行代碼通過字符串形式的類路徑加載指定的Activity類到內(nèi)存中,然后通過反射的newInstance()實例化Activity對象,最后通過向下轉(zhuǎn)型返回給調(diào)用者。
上面這段代碼位于android.app.Instrumentation內(nèi)。同理,我們所熟知的Application,Service,ContentProvider,BroadcatReceiver也是通過這種方式生成的。
Java多態(tài)的偉大之處就從這里開始!
你可能會有疑惑,為什么不直接new呢?
如果是new方法,那么new只能實例化指定的類,也就是說,如果使用new,Android系統(tǒng)框架只能實例化某個Activity了。而如果通過反射,那么只要是Activity的子類,都可以通過此方法實例化,這也就是多態(tài)的精髓。
優(yōu)點
反射涉及到了動態(tài)與靜態(tài)的概念:
- 靜態(tài)編譯:在編譯時確定類型,綁定對象,即通過。
- 動態(tài)編譯:運行時確定類型,綁定對象。動態(tài)編譯最大限度發(fā)揮了java的靈活性,體現(xiàn)了多態(tài)的應用,用以降低類之間的藕合性。
反射機制的優(yōu)點是可以實現(xiàn)動態(tài)創(chuàng)建對象以及修改對象的結構,體現(xiàn)出很大的靈活性。
缺點
它的缺點是對性能有影響。使用反射基本上是一種解釋操作。由于用于字段和方法接入時反射要遠慢于直接代碼,反射在性能上會有所影響,但性能問題的程度取決于程序中是如何使用反射的。如果它作為程序運行中相對很少涉及的部分,緩慢的性能將不會是一個問題。即使測試中最壞情況下的計時圖顯示的反射操作只耗用幾微秒。僅反射在性能關鍵的應用的核心邏輯中使用時性能問題才變得至關重要。所以,合理的使用反射將大大提高我們程序的通用性和復用性。
技術解析鋪墊
運行時類型識別(Run-time Type Identification, RTTI)主要有兩種方式,一種是我們在編譯時和運行時已經(jīng)知道了所有的類型,另外一種是功能強大的”反射”機制。
要理解RTTI在Java中的工作原理,首先必須知道類型信息在運行時是如何表示的,這項工作是由”Class對象”完成的,它包含了與類有關的信息。類是程序的重要組成部分,每個類都有一個Class對象,每當編寫并編譯了一個新類就會產(chǎn)生一個Class對象,它被保存在一個同名的.class文件中。在運行時,當我們想生成這個類的對象時,運行這個程序的Java虛擬機(JVM)會確認這個類的Class對象是否已經(jīng)加載,如果尚未加載,JVM就會根據(jù)類名查找.class文件,并將其載入,一旦這個類的Class對象被載入內(nèi)存,它就被用來創(chuàng)建這個類的所有對象。一般的RTTI形式包括三種:
1.傳統(tǒng)的類型轉(zhuǎn)換。如”(Apple)Fruit”,由RTTI確保類型轉(zhuǎn)換的正確性,如果執(zhí)行了一個錯誤的類型轉(zhuǎn)換,就會拋出一個ClassCastException異常。
2.通過Class對象來獲取對象的類型。如
Class c = Class.forName("Apple");Object o = c.newInstance();3.通過關鍵字instanceof或Class.isInstance()方法來確定對象是否屬于某個特定類型的實例,準確的說,應該是instanceof / Class.isInstance()可以用來確定對象是否屬于某個特定類及其所有基類的實例,這和equals() / ==不一樣,它們用來比較兩個對象是否屬于同一個類的實例,沒有考慮繼承關系。
基本用法
以下分別展示了反射的基本用法:
類的獲取方式
針對我們所知的不同情況分別有3種方法獲取Class對象
當已知類名的時候,通過”類名.class”獲得
當已知對象的時候,通過”對象.getClass”獲得
當已知包括包名在內(nèi)的完整類名(假設為String格式)的時候,可通過”Class.forName(classPath)”或者”ClassLoader.loadClass(classPath)”獲得
比如我們有一個類,類的結構如下:
package com.sahadev;/*** Created by Sahadev on 2017/4/27.*/public class ClassABean {public boolean mFlag;private IMethod mIMethod;public ClassABean() {}public ClassABean(boolean mFlag, IMethod iMethod) {super();this.mFlag = mFlag;this.mIMethod = iMethod;}private void printBValue(){System.out.println("The mFlag = " + mFlag);} }那么類ClassABean的字節(jié)碼的獲取方式有以下3種:
- ClassABean.class;
- new ClassABean().getClass();
- Class.forName(“com.sahadev.ClassABean”);或者ClassLoader.loadClass(“com.sahadev.ClassABean”);
獲取到Class字節(jié)碼對象之后,我們就可以對其進行操作了。
通過無參構造方法實例化對象
通過無參構造的方式有兩種,一種是我們上面看到的,使用newInstance()方法,而另一種是獲得類的無參構造方法,然后通過無參構造方法創(chuàng)建對象。其中newInstance()方法默認調(diào)用的是無參構造方法,如果類沒有無參構造方法,則會有異常拋出。
這兩種方法的使用方式分別如下:
//通過newInstance()方法構造ClassABean instanceA = ClassABean.class.newInstance();//通過無參構造方法構造Constructor<ClassABean> constructor = ClassABean.class.getConstructor();//獲取無參構造方法ClassABean instanceB = constructor.newInstance();//實例化通過有參構造方法實例化對象
通過有參構造方法實例化對象的方法如下:
Constructor<ClassABean> constructor = ClassABean.class.getConstructor(Boolean.class, ClassBBean.class);//獲取指定參數(shù)的構造方法ClassABean instanceB = constructor.newInstance(true, new ClassBBean());//通過對象參數(shù)實例化對象上面的代碼等價于:
ClassABean instanceB = new ClassABean(true, new ClassBBean());通過以上有參構造方法構造的對象,它們的成員屬性現(xiàn)在都已經(jīng)被賦了值。其中屬性mFlag的值為true,屬性mIMethod的實際實現(xiàn)者為ClassBBean。
PS: 在我們的示例中提到的ClassBBean類與ClassCBean類都同樣實現(xiàn)了IMethod接口。
方法調(diào)用
從以上的示例中我們知道了如何通過反射來實例化一個對象,接下來我們通過反射來調(diào)用一下類的私有方法。
在ClassABean類中提供了一個私有方法printBValue(),我們看看如何通過反射來調(diào)用這個方法:
Method method = ClassABean.class.getDeclaredMethod("printBValue");ClassABean instanceB = new ClassABean(true,new ClassBBean());method.setAccessible(true);method.invoke(instanceB);控制臺會正確輸出我們預想中的值:
The mFlag = true這樣調(diào)用和我們通過普通方法調(diào)用的效果是一致的,只是反射可以調(diào)用類的私有方法。
在這里細心的同學就會發(fā)現(xiàn),Class類本身提供了兩個獲取方法的方法,一個是getDeclaredMethod,另一個是getMethod。那這兩者有什么區(qū)別呢?getDeclaredMethod用于獲取所有的方法,包括私有方法。而getMethod則用于獲取public方法,其它權限方法無法獲得。
屬性獲取與賦值
屬性的獲取與方法類同:
Field flagField = ClassABean.class.getDeclaredField("mFlag");flagField.setAccessible(true);ClassABean classABeanInstance = new ClassABean(true, new ClassBBean());boolean flag = (boolean) flagField.get(classABeanInstance);這樣就可以獲得對象classABeanInstance的mFlag的值,同樣的,我們還可以獲得屬性mIMethod的值:
Field iMethodField = ClassABean.class.getDeclaredField("mIMethod");iMethodField.setAccessible(true);ClassABean classABeanInstance = new ClassABean(true, new ClassBBean());IMethod iMethod = (IMethod) iMethodField.get(classABeanInstance);其中IMethod的具體實例為ClassBBean對象。
接下來我們演示一下如何替換屬性的值,這種方式在很多地方都很常見,它的用途很廣:
Field iMethodField = ClassABean.class.getDeclaredField("mIMethod");iMethodField.setAccessible(true);ClassABean classABeanInstance = new ClassABean(true, new ClassBBean());ClassCBean classCBean = new ClassCBean();iMethodField.set(classABeanInstance, classCBean);IMethod iMethod = (IMethod) iMethodField.get(classABeanInstance);通過這樣的方式,我們再獲取mIMethod的值將會是classCBean對象。我們在這里使用了set的方法,set方法用于給指定對象的屬性賦值。
用例1(修改TextView的autoLink的點擊實現(xiàn))
相關文章介紹:如何修改TextView鏈接點擊實現(xiàn)(包含鏈接生成與點擊原理分析)
用例2(熱修復實現(xiàn))
相關文章介紹:一步步手動實現(xiàn)熱修復
擴展了解
通過反射我們可以獲得一個類的注解,它的父類以及實現(xiàn)的接口等。了解反射可以有助于我們實現(xiàn)抽象能力更強的框架。
擴展閱讀:https://developer.android.google.cn/reference/java/lang/Class.html
參考地址
http://c.biancheng.net/cpp/html/1781.html
http://www.voidcn.com/blog/zbuger/article/p-5771880.html
http://www.fanyilun.me/2015/10/29/Java反射原理/
http://rednaxelafx.iteye.com/blog/548536
http://blog.csdn.net/u013551462/article/details/51261817
總結
以上是生活随笔為你收集整理的简明扼要的反射入门教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MVP模式在Android中的应用(附U
- 下一篇: 新闻文本内容知识图谱表示项目