Java進(jìn)階-反射機(jī)制的詳細(xì)學(xué)習(xí)指南

什么是反射

JAVA反射機(jī)制是在運(yùn)行狀態(tài)中装诡,對(duì)于任意一個(gè)類容握,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法信峻;這種動(dòng)態(tài)獲取的以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為Java的反射機(jī)制砌庄。

java反射機(jī)制提供的功能:

  • 在運(yùn)行時(shí)判定任意一個(gè)對(duì)象所屬的類

  • 在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象

  • 在運(yùn)行時(shí)判定任意一個(gè)類所具有的成員變量和方法

  • 在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法

反射應(yīng)用場(chǎng)景

操作因訪問(wèn)權(quán)限限制的屬性和方法
如private屬性和方法舌厨,又如在android開(kāi)發(fā)中虐译,閱讀sdk源碼會(huì)發(fā)現(xiàn)有什么多方法加了hide注解狰挡,是google為了安全起見(jiàn)加的隱藏,應(yīng)用層是無(wú)法直接訪問(wèn)的淑仆,這時(shí)可以通過(guò)反射在運(yùn)行時(shí)調(diào)用(android 9.0 加了白名單機(jī)制涝婉,在黑名單的接口即使通過(guò)反射都無(wú)法調(diào)用)。

實(shí)現(xiàn)自定義注解
android有很多火爆的開(kāi)源框架都應(yīng)用了自定義注解和反射蔗怠,如EventBus墩弯,Retrofit;

動(dòng)態(tài)加載插件
很多大型App都用了動(dòng)態(tài)加載和熱修復(fù)的插件化技術(shù)寞射,也利用了反射的方式渔工,實(shí)現(xiàn)宿主(Host)對(duì)插件(LibPlugin)的調(diào)用,詳細(xì)可以了解插件化開(kāi)源框架桥温,如VirtualApp引矩,DroidPluginRePlugin

Java反射機(jī)制

反射涉及到四個(gè)核心類

  • java.lang.Class.java:類對(duì)象侵浸;

  • java.lang.reflect.Constructor.java:類的構(gòu)造器對(duì)象脓魏;

  • java.lang.reflect.Method.java:類的方法對(duì)象;

  • java.lang.reflect.Field.java:類的屬性對(duì)象通惫;

反射工作原理

要正確理解Java反射機(jī)制就得了解Class這個(gè)類,反射正是對(duì)Class類進(jìn)行操作混蔼。當(dāng)我們編寫好一個(gè)Java程序(Java文件)履腋,會(huì)先將其編譯(生成class文件),而運(yùn)行程序惭嚣,JVM會(huì)加載class文件到內(nèi)存遵湖,并產(chǎn)生一個(gè)Class對(duì)象,通過(guò)這個(gè)Class對(duì)象我們就能獲得加載到虛擬機(jī)當(dāng)中這個(gè)Class對(duì)象對(duì)應(yīng)的父類晚吞、接口延旧、方法、成員以及構(gòu)造方法的聲明和定義等信息槽地,我們通過(guò)new的形式創(chuàng)建對(duì)象實(shí)際上就是通過(guò)這些Class來(lái)創(chuàng)建迁沫。

反射的工作原理就是借助Class.java、Constructor.java捌蚊、Method.java集畅、Field.java這四個(gè)類在程序運(yùn)行時(shí)動(dòng)態(tài)訪問(wèn)和修改任何類的行為和狀態(tài)。


反射的原理

反射常用的API

1缅糟、獲取反射中的Class對(duì)象

在 Java API 中挺智,獲取 Class 類對(duì)象有三種方法:

第一種,使用 Class.forName 靜態(tài)方法窗宦。
當(dāng)你知道該類的全路徑名時(shí)赦颇,你可以使用該方法獲取 Class 類對(duì)象二鳄。

Class clz = Class.forName("java.lang.String");

第二種,使用 .class 方法媒怯。
這種方法只適合在編譯前就知道操作的 Class订讼。

Class clz = String.class;

第三種,使用類對(duì)象的 getClass() 方法沪摄。

String str = new String("Hello");
Class clz = str.getClass();

2躯嫉、通過(guò)反射創(chuàng)建類對(duì)象

通過(guò)反射創(chuàng)建類對(duì)象主要有兩種方式:通過(guò) Class 對(duì)象的 newInstance() 方法、通過(guò) Constructor 對(duì)象的 newInstance() 方法杨拐。

第一種:通過(guò) Class 對(duì)象的 newInstance() 方法祈餐。

Class clz = Stock.class;
Stock stock = (Stock)clz.newInstance();

第二種:通過(guò) Constructor 對(duì)象的 newInstance() 方法

Class clz = Stock.class;
Constructor constructor = clz.getConstructor();
Stock stock = (Stock)constructor.newInstance();

通過(guò) Constructor 對(duì)象創(chuàng)建類對(duì)象可以選擇特定構(gòu)造方法,而通過(guò) Class 對(duì)象則只能使用默認(rèn)的無(wú)參數(shù)構(gòu)造方法哄陶。下面的代碼就調(diào)用了一個(gè)有參數(shù)的構(gòu)造方法進(jìn)行了類對(duì)象的初始化帆阳。

Class clz = Stock.class;
Constructor constructor = clz.getConstructor(String.class, String.class,String.class);
Stock stock= (Stock)constructor.newInstance("000001", "平安銀行","sz");

3、通過(guò)反射獲取類屬性屋吨、方法蜒谤、構(gòu)造器

我們通過(guò) Class 對(duì)象的 getFields() 、getDeclaredFields()方法獲取屬性至扰,通過(guò)getMethod()鳍徽、getDeclaredMethod獲取方法。

\color{red}{Declared}的方法只能獲取類本身包括私有在內(nèi)的屬性或方法敢课,而不帶Declared的方法不能獲取私有屬性或方法阶祭,但包括父類及自身的所有公有屬性或方法。

Class clz = Stock.class;
Field[] fields = clz.getFields();//包括父類的所有公有屬性(不包括私有的)
Field[] declaredFields = clz.getDeclaredFields();//自身的公有直秆、私有屬性

Method[] methods = clz.getMethods();//包括父類的所有公有方法(不包括私有的)
Method[] declaredMethods = clz.getDeclaredMethods();//自身的公有濒募、私有方法

4、通過(guò)反射獲取屬性值及執(zhí)行方法

Class stockClass= Stock.class;
Object stockObject = stockClass.newInstance();

Field marketField = stockClass.getDeclaredField("market");
marketField.setAccessible(true);//私有屬性或方法圾结,必須設(shè)置accessible=true瑰剃,否則拋出IllegalAccessException異常
Log.i(TAG, "reflectPrivateField:" + marketField.get(stockObject));

 Method method = stockClass.getDeclaredMethod("getTrend", String.class);
 method.setAccessible(true);
 Object trend = method.invoke(stockObject, "5");
 Log.i(TAG, "reflectPrivateMethod:" + trend);

完整代碼示例

Prodct.java

public class Product {
    private String mCode;
    private String mName;
    public String type;

    public void setCode(String code) {
        mCode = code;
    }

    public void setName(String name) {
        mName = name;
    }

    public String getCode() {
        return mCode;
    }

    public String getName() {
        return mName;
    }

    public String toString() {
        return "Product{code=" + mCode + ",name=" + mName + ",type=" + type + "}";
    }
}

Stock.java

public class Stock extends Product {

    private String market = "sz";

    private int getTrend(String price) {
        if (TextUtils.isEmpty(price)) {
            return 0;
        } else if (price.contains("-")) {
            return -1;
        } else {
            return 1;
        }
    }

    @Call("do_sth_call")
    public void doSth() {
        Log.i("Stock", "do sth call");
    }

    public String toString() {
        return "Stock{code=" + getCode() + ",name=" + getName() + ",type=" + type + ",market=" + market + "}";
    }
}

Call.java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Call {
    String value();
}

ReflectTest.java

public class ReflectTest {
    public static final String TAG = "reflect";

    public static void testReflect() {
        reflectNewInstance();
        reflectPrivateField();
        reflectPrivateMethod();
        reflectAnnotation();
        reboot();
    }

    /**
     * 創(chuàng)建對(duì)象
     */
    public static void reflectNewInstance() {
        try {
            Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
            Object stockObject = stockClass.newInstance();
            Product product = (Product) stockObject;
            product.setCode("000001");
            product.setName("平安銀行");
            product.type = "1";
            Log.i(TAG, "reflectNewInstance stock=" + product.toString());
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

    /**
     * 反射訪問(wèn)私有屬性
     */
    public static void reflectPrivateField() {
        try {
            Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
            Object stockObject = stockClass.newInstance();
            Field marketField = stockClass.getDeclaredField("market");
            marketField.setAccessible(true);
            Log.i(TAG, "reflectPrivateField:" + marketField.get(stockObject));
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

    /**
     * 反射訪問(wèn)私有方法
     */
    public static void reflectPrivateMethod() {
        try {
            Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
            Object stockObject = stockClass.newInstance();
            Method method = stockClass.getDeclaredMethod("getTrend", String.class);
            method.setAccessible(true);
            Object trend = method.invoke(stockObject, "5");
            Log.i(TAG, "reflectPrivateMethod:" + trend);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

    /**
     * 反射調(diào)用注解方法
     */
    public static void reflectAnnotation() {
        try {
            Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
            Object stockObject = stockClass.newInstance();
            Method[] methods = stockClass.getMethods();
            for (Method method : methods) {
                Call call = method.getAnnotation(Call.class);
                if (call != null && "do_sth_call".equals(call.value())) {
                    method.invoke(stockObject);
                }
            }
        } catch (Exception e) {

        }
    }

    // 重啟手機(jī)
    public static void reboot() {
        try {
            Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
            Method mGetService = cServiceManager.getMethod("getService", String.class);
            Object oPowerManagerService = mGetService.invoke(null, Context.POWER_SERVICE);
            Class<?> cIPowerManagerStub = Class.forName("android.os.IPowerManager$Stub");
            Method mReboot = cIPowerManagerStub.getMethod("reboot", boolean.class, String.class, boolean.class);
            Method mAsInterface = cIPowerManagerStub.getMethod("asInterface", IBinder.class);
            Object oIPowerManager = mAsInterface.invoke(null, oPowerManagerService);
            mReboot.invoke(oIPowerManager, true, null, true);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

反射執(zhí)行reboot方法,會(huì)拋出異常筝野,java.lang.reflect.InvocationTargetException晌姚,異常原因是java.lang.SecurityException: Neither user 10547 nor current process has android.permission.REBOOT。原因是沒(méi)有reboot的操作權(quán)限歇竟。

 Caused by: java.lang.SecurityException: Neither user 10547 nor current process has android.permission.REBOOT.
W/System.err:     at android.os.IPowerManager$Stub$Proxy.reboot(IPowerManager.java:1596)
W/System.err:     at com.android.server.power.PowerManagerService$BinderService.reboot(PowerManagerService.java:5662)

總結(jié)

本文總結(jié)了反射的作用及應(yīng)用場(chǎng)景舀凛,列舉了常用的API,并給出代碼示例途蒋。反射既有優(yōu)點(diǎn)也有缺點(diǎn)猛遍。

優(yōu)點(diǎn)是提供了靈活的機(jī)制,想對(duì)類做啥就做啥,一些黑科技就是用了反射實(shí)現(xiàn)了一般開(kāi)發(fā)無(wú)法做到的功能懊烤,另外很多框架應(yīng)用了反射使得開(kāi)發(fā)更加便利高效梯醒。

缺點(diǎn)是運(yùn)行時(shí)反射操作方法會(huì)比直接調(diào)用性能慢,取決于如何反射腌紧,一般可以忽略茸习,另外一個(gè)問(wèn)題就是安全性,隨意修改私有屬性和訪問(wèn)私有方法壁肋,破壞了類的封裝性号胚,可能潛在邏輯隱患,再一個(gè)是在安卓上應(yīng)用浸遗,可能出現(xiàn)適配問(wèn)題猫胁。因此應(yīng)用反射前要充分了解這些缺點(diǎn)帶來(lái)的影響。

參考

深入淺出反射
https://zhuanlan.zhihu.com/p/21423208
大白話說(shuō)Java反射:入門跛锌、使用弃秆、原理
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
java.lang.Class
java.lang.reflect.Method

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市髓帽,隨后出現(xiàn)的幾起案子菠赚,更是在濱河造成了極大的恐慌,老刑警劉巖郑藏,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衡查,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡必盖,警方通過(guò)查閱死者的電腦和手機(jī)拌牲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)筑悴,“玉大人,你說(shuō)我怎么就攤上這事稍途「罅撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵械拍,是天一觀的道長(zhǎng)突勇。 經(jīng)常有香客問(wèn)我,道長(zhǎng)坷虑,這世上最難降的妖魔是什么甲馋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮迄损,結(jié)果婚禮上定躏,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好痊远,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布垮抗。 她就那樣靜靜地躺著,像睡著了一般碧聪。 火紅的嫁衣襯著肌膚如雪冒版。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天逞姿,我揣著相機(jī)與錄音辞嗡,去河邊找鬼。 笑死滞造,一個(gè)胖子當(dāng)著我的面吹牛续室,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播断部,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼猎贴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蝴光?” 一聲冷哼從身側(cè)響起她渴,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔑祟,沒(méi)想到半個(gè)月后趁耗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疆虚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年苛败,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片径簿。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡罢屈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出篇亭,到底是詐尸還是另有隱情缠捌,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布译蒂,位于F島的核電站曼月,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柔昼。R本人自食惡果不足惜哑芹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捕透。 院中可真熱鬧聪姿,春花似錦碴萧、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至招盲,卻和暖如春低缩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曹货。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工咆繁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顶籽。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓玩般,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親礼饱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坏为,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容