java 反射簡(jiǎn)單記錄

首先通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)看下反射的使用。
先定義一個(gè)測(cè)試類。

package com.example.myapplication;
public class TestClass {

    public  void hi(int age,String name){
        System.out.println("大家好 ,我叫" + name+",今年 " + age+"歲");
    }
}

然后在activity中調(diào)用

        Class testClass = null;
        Method method = null;
        try {
            testClass = Class.forName("com.example.myapplication.TestClass");
            method = testClass.getDeclaredMethod("hi",new Class[]{int.class,String.class});
            method.invoke(testClass.newInstance(),20,"abc");
        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) {
            e.printStackTrace();
        }

最后輸出

I/System.out: 大家好 ,我叫abc,今年 20歲

也就是說(shuō),從Class中在獲取了method(也就是方法名)之后,再調(diào)用method.invoke,這些就相當(dāng)于是新建了一個(gè)TestClass對(duì)象然后調(diào)用這個(gè)對(duì)象的method。
雖然之前有用到過(guò)反射是由于webview加了系統(tǒng)簽名后崩潰然后根據(jù)這篇文章找到了解決辦法,但是對(duì)于反射其實(shí)是不太了解的。大多數(shù)時(shí)候雖然會(huì)用一個(gè)東西但是實(shí)際并沒(méi)有理解其背后的原理。
【工作總結(jié)】系統(tǒng)簽名app運(yùn)行webview(Android5.0+)閃退問(wèn)題
Android源碼中的很多類,是無(wú)法獲取也無(wú)法修改的,例如上面的例子中的WebViewFactory,但是有時(shí)候?yàn)榱诉_(dá)成目的又不得不修改WebViewFactory,不然程序無(wú)法正常運(yùn)行,這種時(shí)候java的反射就會(huì)派上用場(chǎng)。

那么這個(gè)的原理是什么?
首先來(lái)看看這一系列文章:
Java 編程的動(dòng)態(tài)性
這系列文章,探討了從源代碼到執(zhí)行程序所涉及的許多幕后細(xì)節(jié)。
建議通讀一遍。
這里面有幾個(gè)關(guān)鍵字:用二進(jìn)制表示的類,裝入類
用二進(jìn)制表示的類
編譯器將java語(yǔ)言編寫的.java文件編譯,生成一個(gè)二進(jìn)制類(二進(jìn)制類格式實(shí)際上是由 JVM 規(guī)范定義的),然后存儲(chǔ)在擴(kuò)展名為 .class 的文件中。.class 會(huì)被裝入運(yùn)行中的 JVM。

出處:Java 編程的動(dòng)態(tài)性
裝入類:
諸如 C 和 C++ 這些編譯成本機(jī)代碼的語(yǔ)言通常在編譯完源代碼之后需要鏈接這個(gè)步驟。這一鏈接過(guò)程將來(lái)自獨(dú)立編譯好的各個(gè)源文件的代碼和共享庫(kù)代碼合并起來(lái),從而形成了一個(gè)可執(zhí)行程序。使用 Java 語(yǔ)言,由編譯器生成的類在被裝入到 JVM 之前通常保持原狀。
在裝入并初始化類時(shí),JVM 內(nèi)部會(huì)完成許多操作,包括解碼二進(jìn)制類格式、檢查與其它類的兼容性、驗(yàn)證字節(jié)碼操作的順序以及最終構(gòu)造 java.lang.Class 實(shí)例來(lái)表示新類。這個(gè) Class 對(duì)象成了 JVM 創(chuàng)建新類的所有實(shí)例的基礎(chǔ)。它還是已裝入類本身的標(biāo)識(shí) ― 對(duì)于裝入到 JVM 的同一個(gè)二進(jìn)制類,可以有多個(gè)副本,每個(gè)副本都有其自己的 Class 實(shí)例。即使這些副本都共享同一個(gè)類名,但對(duì) JVM 而言它們都是獨(dú)立的類。
這里裝入類看起來(lái)比較別扭,一開始有點(diǎn)抓不準(zhǔn)是名詞還是動(dòng)作。是"裝入類" 還是"裝入 類",后面想想應(yīng)該是指把.class裝入JVM。

看完類和類裝入之后再去看反射會(huì)對(duì)理解反射有一定的幫助。
引入反射

出處:引入反射
使用反射不同于常規(guī)的Java編程,其中它與 元數(shù)據(jù)--描述其它數(shù)據(jù)的數(shù)據(jù)協(xié)作。Java語(yǔ)言反射接入的特殊類型的原數(shù)據(jù)是JVM中類和對(duì)象的描述。反射使您能夠運(yùn)行時(shí)接入廣泛的類信息。它甚至使您能夠讀寫字段,調(diào)用運(yùn)行時(shí)選擇的類的方法。
首先,獲取class。

Class clas = MyClass.class;

這種是在MyClass.java是自己定義的情況下的寫法。當(dāng)需要在運(yùn)行時(shí)從某些外部源讀取類名,即類似上面從Android 源碼中的類名。此時(shí)就需要使用一個(gè)類裝入器來(lái)查找類信息。以下介紹一種方法:

// "name" is the class name to load
Class clas = null;
try {
  clas = Class.forName(name);
} catch (ClassNotFoundException ex) {
  // handle exception case
}
// use the loaded class

如果已經(jīng)裝入了類,您將得到現(xiàn)有的 Class 信息。如果類未被裝入,類裝入器將現(xiàn)在裝入并返回新創(chuàng)建的類實(shí)例。
關(guān)于Class.forName()可以看這一篇:
Class.forName()的作用與使用總結(jié)

獲取了二進(jìn)制格式的.class類之后,下面就是 基于類的反射。

對(duì)于類中的構(gòu)造函數(shù)、字段和方法,java.lang.Class 提供四種獨(dú)立的反射調(diào)用,以不同的方式來(lái)獲得信息。
1.構(gòu)造函數(shù)的反射
首先來(lái)看構(gòu)造函數(shù)的反射調(diào)用。Constructor 的意思是構(gòu)造者。

Constructor getConstructor(Class[] params) 
Constructor[] getConstructors()  
Constructor getDeclaredConstructor(Class[] params) 
Constructor[] getDeclaredConstructors() 

這里返回的是Constructor 或Constructor[]。 那么如何使用?文中是通過(guò)一個(gè)實(shí)例去說(shuō)明getConstructor的使用。

public class TwoString {
    private String m_s1, m_s2;
    public TwoString(String s1, String s2) {
        m_s1 = s1;
        m_s2 = s2;
        System.out.println("m_s1 = " + m_s1 + " , m_s2 = " +m_s2);
    }
}

然后,就是構(gòu)造函數(shù)的反射調(diào)用。Constructor.newInstance就是相當(dāng)于調(diào)用TwoString的構(gòu)造函數(shù)。

Class[] types = new Class[] { String.class, String.class };
Constructor cons = TwoString.class.getConstructor(types);
Object[] args = new Object[] { "a", "b" };
TwoString ts = cons.newInstance(args);

在Android studio中真正運(yùn)行的話要稍微修改一點(diǎn),因?yàn)樯厦娴拇a沒(méi)有加try catch異常所以會(huì)提示報(bào)錯(cuò),按提示自動(dòng)補(bǔ)全就好。

        Class[] types = new Class[] { String.class, String.class };
        Constructor cons = null;
        try {
            cons = TwoString.class.getConstructor(types);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object[] args = new Object[] { "a", "b" };
        try {
            TwoString ts = (TwoString) cons.newInstance(args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

最后運(yùn)行輸出I/System.out: m_s1 = a , m_s2 = b。
也就是說(shuō),上面的那些代碼,完成了構(gòu)造函數(shù)的調(diào)用,也就是一個(gè)類的新建。
2.字段的反射
下面來(lái)看字段的反射。
Class中也提供了4個(gè)方法。

Field getField(String name)  
Field[] getFields()  
Field getDeclaredField(String name)  
Field[] getDeclaredFields()  

下面的實(shí)例是給新增加一個(gè) int 類型的字段賦值。
首先,在TwoString 新增一個(gè)count。

public class TwoString {
    private String m_s1, m_s2;
    public int count;
     public TwoString(String s1, String s2) {
        m_s1 = s1;
        m_s2 = s2;
    }
}

然后新增函數(shù)。

    public int incrementField(String name, Object obj) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(name);
        int value = field.getInt(obj) + 1;
        field.setInt(obj, value);
        return value;
    }

最后調(diào)用

        TwoString mTwoString = new TwoString("a","b");
        try {
            incrementField("count",mTwoString);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Log.d(TAG,"count = " + mTwoString.count);

輸出log如下。

D/MainActivity: count = 1

3.方法的反射
依舊是提供了4個(gè)方法。

Method getMethod(String name, Class[] params)  
Method[] getMethods()  
Method getDeclaredMethod(String name, Class[] params)  
Method[] getDeclaredMethods()  

下面來(lái)看實(shí)例。

   public int incrementProperty(String name, Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
       String prop = Character.toUpperCase(name.charAt(0)) +
               name.substring(1);
       String mname = "get" + prop;
       Class[] types = new Class[] {};
       Method method = obj.getClass().getMethod(mname, types);
       Object result = method.invoke(obj, new Object[0]);
       int value = ((Integer)result).intValue() + 1;
       mname = "set" + prop;
       types = new Class[] { int.class };
       method = obj.getClass().getMethod(mname, types);
       method.invoke(obj, new Object[] { new Integer(value) });
       return value;
   }

定義一個(gè)JavaBean 。

public class JavaBean {
    private int count;

    public int getCount(){
        return count;
    }

    public void setCount(int count){
        this.count = count;
    }
}

最后調(diào)用

       JavaBean javaBean = new JavaBean();
        try {
            incrementProperty("count",javaBean);
         }  catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        Log.d(TAG,"count = " + javaBean.getCount());

輸出log如下。

D/MainActivity: count = 1

因?yàn)槟J(rèn)初始化賦值0,+1后為1所以javaBean中count最后值為1。上面的代碼演示了調(diào)用getCount和setCount的調(diào)用。

以上就是構(gòu)造函數(shù)、字段和方法的反射。
關(guān)于反射,就看到這里為止。還有更多的用法這里暫不深入。

參考鏈接:
類和類裝入
引入反射
結(jié)合反射與 XML 實(shí)現(xiàn) Java 編程的動(dòng)態(tài)性
Class.forName()的作用與使用總結(jié)
【工作總結(jié)】系統(tǒng)簽名app運(yùn)行webview(Android5.0+)閃退問(wèn)題
理解 Android Hook 技術(shù)以及簡(jiǎn)單實(shí)戰(zhàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容