首先通過(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)