書寫代碼必須符合高質量高性能要求,這也是能夠在視覺上和其他程序員拉開差距的技能,同時也是一個優秀程序員的基本要求。
今天我們說一說反射,反射不是設計模式,但是反射機制作為java的基礎之一,在眾多框架的源碼中大量使用,是很多設計模式,框架,組件的重要基礎。比如我們知道的spring aop底層是jdk的動態代理,而動態代理依賴的就是反射機制。
在運行狀態中,對于任意一個類,都能知道這個類的所有屬性和方法;對于任意一個對象都能調用它的任意方法和屬性,這種動態獲取類信息以及動態調用對象方法的功能稱為Java的發射機制。
先了解下類對象的概念:
我們知道類的加載過程為加載 驗證 準備 解析 初始化 銷毀,在加載階段,jvm會根據類的全限定名找到二進制字節流,并把這個二進制字節流加載進內存,轉為運行時數據區的存儲結構,最后創建一個java.lang.Class類型的實例作為方法區這個類型的訪問入口,這里所說的Class實例就是類對象,這個類對象創建完成后會存放在堆區,這個動作是在加載階段完成。
方法區中存放的是類的元數據,包括靜態變量,有哪些屬性,有哪些方法,繼承的父類,實現的接口,異常相關的信息等等,而類對象就是這些信息的訪問入口,Class類提供了很多api,這些api大多是native方法,也就說明這個類對象只是這個類在堆區的一個接口,由jvm底層來實現,jvm底層會根據每個api的功能去方法區拿類的信息。
public static void main(String[] args) { UserService userService = new UserService(); System.out.println("new關鍵字創建對象:"+userService); Class<UserService> userClass= UserService.class; Class userClass1= Class.forName("UserService"); Constructor<?>[] constructors=userClass.getDeclaredConstructors(); Constructor constructor=constructors[0]; Object user=constructor.newInstance("333333","666666"); System.out.println("反射創建對象:"+user;}
通過上面的這個例子,我們可以看到反射是如何應用的:
以上是利用反射機制創建對象,當然除了創建對象,還可以獲取類的屬性實例Field和方法實例Method,通過方法實例和屬性實例的api對對象的方法和屬性進行設置或者執行。這便是反射的應用。
new關鍵字創建對象是加載類完成后接著走創建對象過程,而反射過程是Class.forName()觸發加載類,但是不會創建對象,只有在調用newInstance方法時候才會創建對象,也就是把加載類和創建對象分為兩個部分完成,但是調用newInstance方法創建前必須保證類已經加載完成。
new關鍵字創建對象是靜態編譯,而反射創建對象是動態編譯:
比如下面的代碼,編譯階段是不知道是否要創建UserService類的對象的,所以UserService不會被加載:
public void reflex(String str) { if("UserService".equals(str)){ Class userClass= Class.forName("UserService"); Constructor<?>[] constructors=userClass.getDeclaredConstructors(); Constructor constructor=constructors[0]; Object UserService=constructor.newInstance("333333","666666"); System.out.println(UserService.toString()); } }
以java8為討論基礎,網上所說的反射只能通過無參構造方法創建對象是不正確的,事實證明,反射不僅僅可以通過有參構造方式創建對象,而且還可以通過私有構造方法創建對象,而且這種通過私有構造方法創建對象的方式會破壞單例模式,你想一下,單例模式中的構造方法之所以是私有就是為了不允許外部創建單例對象。而通過反射可以創建的話,那不是違背了單例模式的定理嗎。
例:只是為了說明反射,所有代碼中的單例只是一個簡單的餓漢式單例:
public class IdGenerator { private String k; private static final IdGenerator instance = new IdGenerator(); private IdGenerator(String k) { this.k=k; } public static IdGenerator getInstance() { return instance; } } public class reflex { public static void main(String[] args){ Class idGeneratorClass= Class.forName("IdGenerator"); Constructor<?>[] constructors=idGeneratorClass.getDeclaredConstructors(); Constructor constructor=constructors[0]; constructor.setAccessible(true);//暴力反射,可以突破私有權限 Object idGenerator=constructor.newInstance("333333"); System.out.println(idGenerator==IdGenerator.getInstance()); }}
這個例子既驗證了反射調用有參構造方法創建實例,又驗證了反射破壞單例模式。
反射會造成泛型擦除:
List<UserService> list=new ArrayList<UserService>(); list.add(new UserService()); list.add(new UserService()); list.add(new UserService()); Class<? extends List> listClass=list.getClass(); Method method=listClass.getDeclaredMethod("add",Object.class); method.invoke(list,"123"); for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); }
上面的例子中通過反射創建的list對象,在調用add方法的時候不會限制類型,導致無法用某個類型去接收list集合中數據,否則會報類型轉換異常,這種情況只能直接返回前端。
效率問題 ,反射的效率比new字段創建對象的效率低很多,因此在使用的時候要特別注意性能問題,但是即便是這樣,我們寫出的代碼主要的性能影響點很少是反射造成,而大多情況是因為代碼結構框架,函數,工具,底層原理的不合理使用造成的。因此發射機制可以用在代碼中,但是要用在合適的位置。
現在來總結下反射的作用:
可以在程序運行過程中去操作字節碼文件和類對象進而進行得到類信息,創建對象以及執行對象方法等,不需要重新編譯,提高程序的擴展性 復用性 解耦
反射相關的四個類,這些類的中的方法底層大多是native方法,所以反射其實是jvm底層實現。掌握了這四個類,靈活運用,基本就掌握了反射機制。
field是類中的成員變量:變量和屬性是倆個概念,變量有get和set方法就是屬性。
newInstance(object arg...) 傳入參數的時候,會調用有對應參數的構造方法創建對象,不傳參數就是使用默認構造方法。
setAccessible() 暴力反射,忽略訪問權限修飾符:
Class userClass= Class.forName("UserService");Constructor<?>[] constructors=userClass.getDeclaredConstructors();Constructor constructor=constructors[0];Object UserService=constructor.newInstance("333333","666666");
所謂暴力反射,就是當類中有私有構造方法,私有屬性,私有方法的時候,對這些對象進行反射調用的時候會報錯,原因是無法突破私有權限,反射調用前先調用對象的setAccessible方法,設置為true,就可以突破私有權限,代碼可以看上面破壞單例的例子。
本文鏈接:http://www.tebozhan.com/showinfo-26-16379-0.html你真的了解Java的反射機制嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: Go 使用環境變量