反射
JAVA 反射機制是在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動态擷取的資訊以及動态調用對象的方法的功能稱為 java 語言的反射機制。
反射可以用來生成動态代理。
反射機制的相關類
對于一個位元組碼檔案.class,雖然表面上我們對該位元組碼檔案一無所知,但該檔案本身卻記錄了許多資訊。Java在将.class位元組碼檔案載入時,JVM将産生一個java.lang.Class對象代表該.class位元組碼檔案,從該Class對象中可以獲得類的許多基本資訊,這就是反射機制。是以要想完成反射操作,就必須首先認識Class類。
反射機制所需的類主要有java.lang包中的Class類和java.lang.reflect包中的Constructor類、Field類、Method類和Parameter類。Class類是一個比較特殊的類,它是反射機制的基礎,Class類的對象表示正在運作的Java程式中的類或接口,也就是任何一個類被加載時,即将類的.class檔案(位元組碼檔案)讀入記憶體的同時,都自動為之建立一個java.lang.Class對象。Class類沒有公共構造方法,其對象是JVM在加載類時通過調用類加載器中的defineClass()方法建立的,是以不能顯式地建立一個Class對象。通過這個Class對象,才可以獲得該對象的其他資訊。
每個類被加載之後,系統都會為該類生成一個對應的Class對象,通過Class對象就可以通路到JVM中該類的資訊,一旦類被加載到JVM中,同一個類将不會被再次載入。被載入JVM的類都有一個唯一辨別就是該類的全名,即包括包名和類名。在Java中程式獲得Class對象有如下3種方式。
(1)使用Class類的靜态方法forName(String className),其中參數className表示所需類的全名。如“Class cObj=Class.forName(“java.lang.String”);”。另外,forName()方法聲明抛出ClassNotFoundException異常,是以調用該方法時必須捕獲或抛出該異常。
(2)用類名調用該類的class屬性來獲得該類對應的Class對象,即“類名.class”。如,語句“Class cObj=Cylinder.class;”将傳回Cylinder類所對應的Class對象賦給cObj變量。
(3)用對象調用getClass()方法來獲得該類對應的Class對象,即“對象.getClass()”。該方法是Object類中的一個方法,是以所有對象調用該方法都可以傳回所屬類對應的Class對象。如例8.8中的語句“Person per=new Person(“張三”);”可以通過以下語句傳回該類的Class對象:Class cObj=per.getClass();
通過類的class屬性獲得該類所對應的Class對象,會使代碼更安全,程式性能更好,是以大部分情況下建議使用第二種方式。但如果隻獲得一個字元串,例如獲得String類對應的Class對象,則不能使用String.class方式,而是使用Class.forName(“java.lang.String”)。注意:如果要想獲得基本資料類型的Class對象,可以使用對應的打包類加上.TYPE,例如,Integer.TYPE可獲得int的Class對象,但要獲得Integer.class的Class對象,則必須使用Integer.class。在獲得Class對象後,就可以使用getClass()方法來取得Class對象的基本資訊。
反射包reflect中的常用類
反射機制中除了上面介紹的java.lang包中的Class類之外,還需要java.lang.reflect包中的Constructor類、Method類、Field類和Parameter類。Java 8以後在java.lang.reflect包中新增了一個Executable抽象類,該類對象代表可執行的類成員。Executable抽象類派生了Constructor和Method兩個子類。
java.lang.reflect.Executable類提供了大量方法用來擷取參數、修飾符或注解等資訊,以下是Executable的一些常用方法
//傳回所有參數的類型
public abstract Class<?>[] getParameterTypes();
//傳回參數的個數
public int getParameterCount()
//傳回所有形參
public Parameter[] getParameters()
//傳回整數表示的修飾符關鍵字常量
public abstract int getModifiers();
getModifiers()方法傳回的是以整數表示的修飾符。此時引入Modifier類,通過調用Modifier.toString(int mod)方法傳回修飾符常量所應的字元串。
例如:Modifier.FINAL
public static final int FINAL = 0x00000010;//是用16進制的0x00000010表示Modifier.FINAL,數值為16
java.lang.reflect.Constructor類是java.lang.reflect.Executable類的直接子類,用于表示類的構造方法。通過Class對象的getConstructors()方法可以獲得目前運作時類的構造方法。以下是Constructor類的常用方法
//傳回構造函數的名稱
public String getName() {
return getDeclaringClass().getName();
}
public T newInstance(Object … initargs)通過該構造方法建立一個對象,initargs為構造方法所需的參數,如果沒有參數則表示無參構造方法
public void setAccessible(boolean flag) //私有構造方法是不允許通過反射來建立對象的,但是如果先執行該方法,傳參為true,則允許建立
java.lang.reflect.Method類是java.lang.reflect.Executable類的直接子類,用于封裝成員方法的資訊,調用Class對象的getMethod()方法或getMethods()方法可以獲得目前運作時類的指定方法或所有方法。以下是java.lang.reflect.Method類的常用方法。
//傳回方法的名稱
public String getName()
//跟構造方法的setAccessible用法一樣
public void setAccessible(boolean flag)
//傳回方法傳回值的類型
public Class<?> getReturnType() {
return returnType;
}
//重寫的equals方法
public boolean equals(Object obj) {
//判斷傳入對象是否為空,是否為Method類型
if (obj != null && obj instanceof Method) {
Method other = (Method)obj;
//比較類型和名字
if ((getDeclaringClass() == other.getDeclaringClass())
&& (getName() == other.getName())) {
//比較傳回值
if (!returnType.equals(other.getReturnType()))
return false;
//比較參數是否相等
return equalParamTypes(parameterTypes, other.parameterTypes);
}
}
return false;
}
//使用傳入的對象和參數執行方法
public Object invoke(Object obj, Object… args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
java.lang.reflect.Field類用于封裝成員變量資訊,調用Class對象的getField()方法或getFields()可以獲得目前運作時類的指定成員變量或所有成員變量。java.lang.reflect.Field類的常用方法如下面所示。
//傳回成員變量的名稱
public String getName() {
return name;
}
//傳回成員變量的類型
public Class<?> getType() {
return type;
}
//傳回成員變量的值
public Object get(Object obj)
throws IllegalArgumentException, IllegalAccessException
//傳回成員變量的值
public boolean getBoolean(Object obj)
throws IllegalArgumentException, IllegalAccessException
//給對象中的成員變量指派
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
//傳回成員變量的類型
public Class<?> getType() {
return type;
}
java.lang.reflect.Parameter類是參數類,每個Parameter對象代表方法的一個參數。java.lang.reflect.Parameter類中提供了許多方法來擷取參數資訊,以下是java.lang.reflect.Parameter類的常用方法。
//傳回修飾符的數值表示,使用Modifier的toString方法,可以得到名稱
public int getModifiers() {
return modifiers;
}
//傳回參數的名稱
public String getName() {
return name;
}
//傳回參數的類型
public Class<?> getType()
//是否為可變參數
public boolean isVarArgs() {
return executable.isVarArgs() &&
index == executable.getParameterCount() - 1;
}
例子
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* 擷取TestObject類的Class對象并且建立TestObject類執行個體
*/
Class<?> tagetClass = Class.forName("cn.mytest.TestObject");
TestObject targetObject = (TestObject) tagetClass.newInstance();
/**
* 擷取所有類中所有定義的方法
*/
Method[] methods = tagetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* 擷取指定方法并調用
*/
Method publicMethod = tagetClass.getDeclaredMethod("testMethod",
String.class);
publicMethod.invoke(targetObject, "hello world");
/**
* 擷取指定成員變量并進行修改
*/
Field field = tagetClass.getDeclaredField("name");
//為了對類中的參數進行修改我們取消安全檢查
field.setAccessible(true);
field.set(targetObject, "my");
/**
* 調用 private 方法
*/
Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
//為了調用private方法我們取消安全檢查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
注意:
getxxxx和getDeclaredxxxx的差別在于是否傳回父類的相關資訊,下面是getMethods()和getDeclaredMethods()的差別
1:getMethods(),該方法是擷取本類以及父類或者父接口中所有的公共方法(public修飾符修飾的)
2:getDeclaredMethods(),該方法是擷取本類中的所有方法,包括私有的(private、protected、預設以及public)的方法。(不包含父類方法)
優點:
反射機制極大的提高了程式的靈活性和擴充性,降低子產品的耦合性,提高自身的适應能力。
通過反射機制可以讓程式建立和控制任何類的對象,無需提前寫死目标類。
使用反射機制能夠在運作時構造一個類的對象、判斷一個類所具有的成員變量和方法、調用一個對象的方法。
反射機制是建構架構技術的基礎所在,使用反射可以避免将代碼寫死在架構中。
正是反射有以上的特征,是以它能動态編譯和建立對象,極大的激發了程式設計語言的靈活性,強化了多态的特性,進一步提升了面向對象程式設計的抽象能力,因而受到程式設計界的青睐
缺點:
1、性能較差
2、安全問題