什麼是反射
“反射”之中包含了一個“反”字,我們先來看看什麼是“正”;一般情況下,我們使用某個類會知道他是幹什麼的,裏面有哪些屬性和方法,在使用時對這個類實例化,之後用實例化的對象來操作。
反射,則是一開始我們不知道要使用的類是什麼,所以不能使用new關鍵字來實例化,更不會知道類中包含了哪些方法、屬性。這時候就需要使用JDK提供的“反射”API進行反射調用。
反射就是在運行時才知道要操作的類是什麼,在運行時獲取完整的構造,屬性,並調用方法。
反射也是Java語言被視爲動態語言的關鍵。
Class類
Class是一個類,封裝了當前對象所對應的類的信息。我們寫的所有的類,其實都是一個Class的對象,Class就是來描述所有的類。類的所有信息,會在編譯的時候加在java類文件的末尾,包括它的構造,方法,屬性。
獲取Class類
獲取Class類的方法有三種:
- 通過類名獲取 —— 類名.Class
- 通過對象名獲取 —— 對象名.Class
- 通過全類名獲取 —— Class.forName(“com.xxx.xxx”)
通過new關鍵字創建的對象,和通過Class創建的對象是完全一樣的:
Person p = new Person("shy",23);
p.getAge();
Log.e("通過new創建Person", "name = " + p.getName());
//通過下面 三種方式創建的Class對象是一樣的
//Class class1 = Person.class;
//Class class2 = p.getClass();
try { //全類名可能找不到 android裏需要try catch
Class class3 = Class.forName("com.example.study.Person");
Person person = (Person) class3.newInstance();
person.setName("SHY");
Log.e("通過Class創建Person", "name = " + person.getName());
} catch (Exception e) {
e.printStackTrace();
}
輸出:
這裏要說明一下,在Java中類的信息(構造,屬性,方法)都有對應的類來表示(Field:屬性,Constructor:構造,Method:方法)。通過這些類的對象,我們可以獲取到類的信息。
通過反射獲取構造方法
首先我們先創建一個Person類,裏面包含一些屬性和方法
public class Person {
String name;
private int age;
public Person(){
super();
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void printAll(){
Log.e("printAll被調用","printAll被調用");
}
}
獲取構造方法的思路,根據上面的圖片,我們可以得知Constructor對象包含在Class中,所以我們獲取到類的Class對象,在獲取Constructor即可
try {
Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("獲取全部構造函數","——————————");
Constructor<Person>[] constructors = (Constructor<Person>[]) c.getConstructors();
for (Constructor<Person> constructor : constructors){
Log.e("構造函數:", "" + constructor);
}
} catch (Exception e) {// 爲了方便我就直接寫Exception了
e.printStackTrace();
}
輸出結果:
我們通過getConstructors可以獲取到類的全部構造函數,那如何獲取指定構造?
//通過getDeclaredConstructor方法 傳入想獲取的構造函數對應的參數類型即可
//這裏不再需要封裝類型,基礎類型直接 .class 即可
Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("獲取指定構造函數", "——————————");
Log.e("構造函數:", "" + constructor);
輸出結果:
現在已經可以獲取構造方法,那麼該如何調用呢?
Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("獲取指定構造函數", "——————————");
Log.e("構造函數:", "" + constructor);
Person person = constructor.newInstance("Shy", 23);
Log.e("調用構造函數之後","name = " + person.getName());
Log.e("調用構造函數之後","age = " + person.getAge());
輸出結果:
通過反射獲取方法
獲取方法和獲取構造方法大體上思路是一樣的,就是個別方法會有所不同,同樣需要先獲取Class對象,通過Class對象獲取Method
Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("獲取全部方法","不包括私有方法!!!");
Method[] methods = c.getMethods();
for (Method method : methods){
Log.e("方法:", "" + method);
}
輸出結果:
通過上面的代碼,會發現Person類中printAll()
方法並沒有被打印出來,這就說明getMethods()
能獲取除私有方法外的所有方法,那麼如何獲取私有方法呢?
Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("獲取當前類的方法","不包括父類方法!!!");
Method[] methods = c.getDeclaredMethods();
for (Method method : methods){
Log.e("方法:", "" + method);
}
輸出結果:
通過getDeclaredMethods()
方法能夠獲取當前類的所有方法,包括私有方法,但是父類的方法無法獲取。
當然,和獲取構造方法同樣,也可以根據參數獲取指定方法,調用getDeclaredMethod(name, paramType)
方法可以獲取指定方法。
//需要傳入 參數名 參數類型, 如果沒有參數可以省去
Method method = c.getDeclaredMethod("printAll");
Log.e("獲取指定方法", "如果有參數,後面需要傳入對應的參數類型");
Log.e("指定方法:", "" + method);
最後來說一下如何調用獲取到的方法,再Method類中,有一個invoke方法,即爲調用方法。
Method method = c.getDeclaredMethod("printAll");
Log.e("獲取指定方法", "如果有參數,後面需要傳入對應的參數類型");
Log.e("指定方法:", "" + method);
//要執行方法的對象
Object obj = c.newInstance();
//這裏要注意下,如果被調用方法是私有方法必須調用setAccessible設爲true!!!
method.setAccessible(true);
//如果調用的方法有參數,後面要跟上參數值
method.invoke(obj);
輸出結果:
通過反射獲取屬性
屬性的獲取、調用和方法的很相似就不過多贅述了直接上代碼
通過getDeclaredFields獲得當前類的全部屬性,包括私有屬性,但不包括父類屬性
Log.e("獲取當前類屬性","不包括父類的屬性!!!");
Field[] fields = c.getDeclaredFields();
for(Field field : fields){
Log.e("屬性:", "" + field);
}
輸出結果:
獲取指定屬性:
Log.e("獲取指定屬性","獲取指定屬性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("獲取指定屬性name:","" + field.get(person));
輸出結果:
修改指定屬性:
Log.e("獲取指定屬性","獲取指定屬性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("獲取指定屬性name:","" + field.get(person));
Log.e("修改指定屬性","私有屬性需要調用setAccessible並且設爲true");
//如果屬性是私有屬性 也需要調用setAccessible(true)
//field.setAccessible(true);
field.set(person, "Shy");
Log.e("修改指定屬性name:","" + field.get(person));
輸出結果:
靜態代理模式
想象一個場景:有一家男裝工廠ManFactory
生產男裝,一家男裝商城ManShop
售賣男裝;張三想從這家男裝商城購買一件黑色的男裝,但是距離太遠他沒法去,只能通過代購Person
去買,代購還可以給他提供售前售後服務。
根據這樣的場景,我們編寫一下基礎代碼:
男裝工廠 ManFactory
//男裝工廠 生產男裝
public interface ManFactory {
public void buyManClothes(String color);
}
男裝商城 ManShop
//男裝商城售賣男裝
public class ManShop implements ManFactory {
//商場買衣服事件
@Override
public void buyManClothes(String color) {
Log.e("男裝商城ManShop","買了一件 "+ color +" 男裝");
}
}
代購人Person
//代購Person 買衣服可以提供售前 售後服務
public class Person implements ManFactory {
ManFactory manFactory; //必須持有真實對象
public Person(ManFactory manFactory){
this.manFactory = manFactory;
}
/** 售前服務 前置處理器 **/
public void before(){
Log.e("代購Person","售前服務");
}
/** 售後服務 後置處理器 **/
public void after(){
Log.e("代購Person","售後服務");
}
//代購買衣服事件
@Override
public void buyManClothes(String color) {
//售前服務
before();
//代購去商場買衣服
manFactory.buyManClothes(color);
//售後服務
after();
}
}
當張三找代購去購買男裝商場的男裝時
//張三告訴代購 去哪個商場買
ManFactory manFactory = new ManShop();
Person person = new Person(manFactory);
//代購根據張三的要求 去買 並且提供 售前售後服務
person.buyManClothes("黑色");
輸出結果:
以上的代碼就完成了簡單的靜態代理,靜態代理的缺點在於,如果此時,李四想要從女裝商場買一件女裝,他也去找Person代購,這樣一來,我們還得去修改Person,讓Person去實現WomanFactory,如果男裝商場購買條件不是顏色了,改爲Int類型的碼數了,那改動量就大了。所以靜態代理的缺點顯而易見,維護性差,可拓展性差。
動態代理模式
爲了解決靜態代理的種種缺點,動態代理出現了。接着上面的需求,李四又要買女裝;我們先創建女裝對應的工廠和商場
女裝工廠 WomanFactory
//女裝工廠
public interface WomanFactory {
public void buyWomanClothes(String color);
}
女裝商場 WomanShop
public class WomanShop implements WomanFactory {
@Override
public void buyWomanClothes(String color) {
Log.e("女裝商城WomanShop","買了一件 "+ color +" 女裝");
}
}
顯而易見,如果還去找Person代購,是滿足不了需求的,這時就出現了一家代購公司,可以代購多個商場的衣服。
代購公司 Company
public class Company implements InvocationHandler {
Object shop; //持有的真實對象
public Object getShop() {
return shop;
}
public void setShop(Object shop) {
this.shop = shop ;
}
public Object getPoxyInstance(){
//newProxyInstance接受三個參數
//1. 要代理對象的類加載器
//2. 要代理對象的類實現的接口(根據反射得到,反射中沒有說獲取接口的方式,根據反射獲取方法舉一反三)
//3. 被代理對象要進行的操作(重寫的invoke方法)
return Proxy.newProxyInstance(shop.getClass().getClassLoader(),
shop.getClass().getInterfaces(),
this);
}
/** 售前服務 前置處理器 **/
public void before(){
Log.e("代購公司","售前服務");
}
/** 售後服務 後置處理器 **/
public void after(){
Log.e("代購公司","售後服務");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//售前服務
before();
//去代購 去商場買衣服
Object proxyResult = method.invoke(shop,args);
//售後服務
after();
return proxyResult;
}
}
那麼此時,張三李四同時去找代購公司去買衣服用如下代碼表示:
//張三要買黑色男裝
//告訴代購公司 買哪個商場的衣服
ManFactory manFactory = new ManShop();
Company company = new Company();
company.setShop(manFactory);
//代購公司 派員工 person1 去代購
ManFactory person1 = (ManFactory) company.getPoxyInstance();
//員工 person1 根據張三的要求去買男裝 並且完成售前售後服務
person1.buyManClothes("黑色");
//李四 要買紅色女裝
//告訴代購公司 買哪個商場的衣服
WomanFactory womanFactory = new WomanShop();
company.setShop(womanFactory);
//代購公司 派員工 person2 去代購
WomanFactory person2 = (WomanFactory) company.getPoxyInstance();
//員工 person2 根據李四的要求去買女裝 並且完成售前售後服務
person2.buyWomanClothes("紅色");
輸出結果:
總結一下:學習反射和動態代理模式,更多的在於能夠讀懂一些主流的框架,很多框架都用動態代理封裝了一些操作供我們使用,比如:Retrofit,Retorfit就是將我們定義的網絡請求接口通過動態代理翻譯成網絡請求,再通過okhttp去請求。