Java反射是Java語言一個很重要的特徵,簡單剖析下反射的定義、原理、使用、性能及應用場景。
(一)定義
程序運行時,允許改動程序結構或變量類型,這種語言稱爲動態語言。java不屬於動態語言,但提供了RTTI(Run-time Type Identification)運行時類別識別。RTTI分爲兩種方式,一種是編譯運行時已知悉類型,一種是反射機制。
(二)原理
在《深入java虛擬機》中提到,java文件被編譯成class文件,JVM類加載器加載class字節碼到方法區,然後在堆中生成Class類,Class類可以訪問到類的基本信息,如類簡單名、類包含路徑全名、訪問修飾符、字段、方法等信息。
反射中需要使用到的類:
Field類:提供有關類或接口的屬性的信息,以及對它的動態訪問權限。反射的字段可能是一個類(靜態)屬性或實例屬性,簡單的理解可以把它看成一個封裝反射類的屬性的類。
Constructor類:提供關於類的單個構造方法的信息以及對它的訪問權限。這個類和Field類不同,Field類封裝了反射類的屬性,而Constructor類則封裝了反射類的構造方法。
Method類:提供關於類或接口上單獨某個方法的信息。所反映的方法可能是類方法或實例方法(包括抽象方法)。
Class類:類的實例表示正在運行的 Java 應用程序中的類和接口。枚舉是一種類,註釋是一種接口。每個數組屬於被映射爲 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class 對象。
(三)使用
(1)獲取Class
方法一:Class c=Class.forName("java.lang.String")
方法二:對於基本數據類型可以用形如Class c=int.class或Class c=Integer.TYPE的語句
(tips:int.class = Integer.TYPE !=Integer.class)
方法三:Class c=MyClass.class
(2)調用Class中的方法得到你想得到的信息集合,如調用getDeclaredFields()方法得到類所有的屬性
Field field = classInstance.getDeclaredField("TEST_TIMES");
int times = (Integer) field.get(classInstance);
System.out.println(times);
反射的性能是低於直接調用的,下次通過測試驗證這個結果,測試中儘量避免對象創建等干擾因素。
我們將測試直接訪問的耗時、直接反射的耗時、緩存需要查找的函數反射的耗時、使用ReflectAsm的反射耗時。
/**
* 測試反射性能
*
* @author peter_wang
* @create-time 2014-6-13 下午12:54:52
*/
public class ReflectPerformanceDemo {
private static final int TEST_TIMES = 1000000;
private long mNum;
private long mSum;
/**
* @param args
*/
public static void main(String[] args) {
normalInvoke();
normalReflectInvoke();
cacheReflectInvoke();
asmReflectInvoke();
}
/**
* 正常調用方法
*/
private static void normalInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
long time1 = System.currentTimeMillis();
for (long i = 0; i < TEST_TIMES; i++) {
demo.setmNum(i);
demo.mSum += demo.getmNum();
}
long time2 = System.currentTimeMillis();
System.out.println("normal invoke time:" + (time2 - time1));
}
/**
* 常規反射調用方法
*/
private static void normalReflectInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
long time1 = System.currentTimeMillis();
try {
for (long i = 0; i < TEST_TIMES; i++) {
Class<?> c = Class.forName("com.peter.demo.process.reflect.ReflectPerformanceDemo");
Method method = c.getMethod("setmNum", Long.TYPE);
method.invoke(demo, i);
demo.mSum += demo.getmNum();
}
}
catch (Exception e) {
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
System.out.println("normal reflect invoke time:" + (time2 - time1));
}
/**
* 緩存反射調用方法
*/
private static void cacheReflectInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
try {
Class<?> c = Class.forName("com.peter.demo.process.reflect.ReflectPerformanceDemo");
Method method = c.getMethod("setmNum", Long.TYPE);
long time1 = System.currentTimeMillis();
for (long i = 0; i < TEST_TIMES; i++) {
method.invoke(demo, i);
demo.mSum += demo.getmNum();
}
long time2 = System.currentTimeMillis();
System.out.println("cache invoke time:" + (time2 - time1));
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* asm反射調用方法
*/
private static void asmReflectInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
try {
MethodAccess ma = MethodAccess.get(ReflectPerformanceDemo.class);
int index = ma.getIndex("setmNum");
long time1 = System.currentTimeMillis();
for (long i = 0; i < TEST_TIMES; i++) {
ma.invoke(demo, index, i);
demo.mSum += demo.getmNum();
}
long time2 = System.currentTimeMillis();
System.out.println("asm invoke time:" + (time2 - time1));
}
catch (Exception e) {
e.printStackTrace();
}
}
public long getmNum() {
return mNum;
}
public void setmNum(long mNum) {
this.mNum = mNum;
}
}
測試結果:
normal invoke time:7
normal reflect invoke time:1499
cache invoke time:32
asm invoke time:20
帶緩存的反射調用方法速度明顯慢於直接調用,採用asm第三方反射庫,速度有少量提升。
反射慢的原因:Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
(五)應用場景
(1)“基於構件的編程”,在這種編程方式中,將使用某種基於快速應用開發(RAD)的應用構建工具來構建項目。這是現在最常見的可視化編程方法,通過代表不同組件的圖標拖動到圖板上來創建程序,然後設置構件的屬性值來配置它們。這種配置要求構件都是可實例化的,並且要暴露其部分信息,使得程序員可以讀取和設置構件的值。
(2)能夠提供在跨網絡的遠程平臺上創建和運行對象的能力,實現java語言的網絡可移動性。這被成爲遠程調用(RMI),它允許一個Java程序將對象分步在多臺機器上,這種分步能力將幫助開發人員執行一些需要進行大量計算的任務,充分利用計算機資源,提高運行速度。
(六)範例
破解最可靠的單例模式,在這個例子中,最可靠的又可以lazy loading的是第五種單例模式創建,但是可以通過反射機制破除安全性。
/**
* 安全的單例模式
*
* @author peter_wang
* @create-time 2014-6-10 下午4:45:20
*/
public class SingletonSafe {
private SingletonSafe() {
System.out.println("create singleton");
}
private static class StaticSingleton {
private static SingletonSafe instance = new SingletonSafe();
}
public static SingletonSafe getInstance() {
return StaticSingleton.instance;
}
}
/**
* 測試反射
*
* @author peter_wang
* @create-time 2014-6-10 下午5:08:58
*/
public class ReflectDemo {
/**
* @param args
*/
public static void main(String[] args) {
try {
Class classInstance = Class.forName("com.peter.demo.process.reflect.SingletonSafe");
System.out.println(classInstance.getName());
Constructor cons = classInstance.getDeclaredConstructor(null);
cons.setAccessible(true);
SingletonSafe singletonSafe1 = (SingletonSafe) cons.newInstance(null);
System.out.println("singleton1:" + singletonSafe1.toString());
SingletonSafe singletonSafe2 = (SingletonSafe) cons.newInstance(null);
System.out.println("singleton2:" + singletonSafe2.toString());
Method method1 = classInstance.getDeclaredMethod("getInstance", null);
SingletonSafe singletonSafe3 = (SingletonSafe) method1.invoke(classInstance, null);
System.out.println("singleton3:" + singletonSafe3.toString());
Method method2 = classInstance.getDeclaredMethod("getInstance", null);
SingletonSafe singletonSafe4 = (SingletonSafe) method2.invoke(classInstance, null);
System.out.println("singleton4:" + singletonSafe4.toString());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
測試結果:
com.peter.demo.process.reflect.SingletonSafe
create singleton
singleton1:com.peter.demo.process.reflect.SingletonSafe@2a9931f5
create singleton
singleton2:com.peter.demo.process.reflect.SingletonSafe@2f9ee1ac
create singleton
singleton3:com.peter.demo.process.reflect.SingletonSafe@5f186fab
singleton4:com.peter.demo.process.reflect.SingletonSafe@5f186fab
由測試結果可知,
單例模式可以用反射創建多個對象。
(七)總結
目前的計算機系統的速度,應用開發已不在過於在意性能,而更爲注重系統的可維護性和擴展性以及快速開發效率上。上述的測試結果是在大量操作基礎上產生的。而在通常的一次業務請求中,反射使用的次數應該是非常少的,只在框架級基礎上被使用,在一個高負載的系統中,業務處理的性能將是關鍵點,而不在於使用的這些反射所帶來的性能影響上。而使用反射所帶來的開發便利與可維護性可擴展性的提升所帶來的價值,是遠遠高於其所損耗的性能的。