重走JAVA程式設計之路(三)反射

東陸之滇發表於2019-03-07

Java反射給我們提供了在執行時檢查甚至修改應用行為的機制。 反射是java高階的核心技術,所有有經驗的程式設計師都應該理解。

通過反射機制,我們可以在執行時檢視 類、介面、列舉,獲得他們的結構、方法以及屬性資訊,即使在編譯期類是不可訪問的。 我們也可以通過反射建立類例項,呼叫它的方法,或者改變屬性值。

Java中的反射

Java的反射是一種很強大的機制,在正常的程式設計中使用並不多,但它是java的主幹,很多Java EE 框架均使用了反射技術:

  • JUnit 利用反射技術解析@Test註解,從而得到測試的方法並呼叫它們。

  • Spring 依賴注入是java反射的典型應用

  • Tomcat web容器通過解析web.xml檔案和請求url,將請求正確的轉發到對應的模組。

  • Eclipse 自動完成方法的名稱輸入

  • Struts

  • Hibernate

以上這個清單只是小部分,它們全部使用了反射技術,因為正常情況下,它們無法訪問使用者編寫的類、介面以及方法等。

但是我們不建議在正常程式設計中濫用反射技術,因為我們擁有自己編寫的類的訪問許可權了,反射存在以下幾個缺陷:

  • 效能較差 儘管反射解決了動態型別的問題,但是也引入了在classpath 掃描類進行載入的過程,會影響效能。

  • 安全限制 反射需要在執行時獲得訪問許可權,但是在security manager中可能是不允許的。 這可能會導致應用執行失敗。

  • 安全問題 通過反射我們可以訪問那些不建議我們訪問的類,例如我們可以訪問private的屬性並修改其值。 這可能引發安全問題導致應用異常。

  • 較高的維護代價 反射相關的程式碼難以理解以及除錯,程式碼的錯誤不能在編譯期展現出來,使用反射的程式碼靈活性不高並難以維護。

反射在類中的使用

在java中,任何物件要麼是原始型別或者引用型別。 所有的類、列舉、資料和其他引用型別均繼承自Object類。

java.lang.Class是所有反射操作的入口。對於任何型別的物件,JVM 會初始化其一個不可變的java.lang.Class 例項來提供檢查物件的執行時的屬性、建立新物件、呼叫方法、get/set 屬性。

我們來看看Class的重要方法,為了方便起見,我們先建立一些類和介面。

package com.byron4j.hightLevel.reflection;

public interface BaseInterface {
	
	public int interfaceInt=0;
	
	void method1();
	
	int method2(String str);
}

複製程式碼
package com.byron4j.hightLevel.reflection;

public class BaseClass {
	
public int baseInt;
	
	private static void method3(){
		System.out.println("Method3");
	}
	
	public int method4(){
		System.out.println("Method4");
		return 0;
	}
	
	public static int method5(){
		System.out.println("Method5");
		return 0;
	}
	
	void method6(){
		System.out.println("Method6");
	}
	
	// piblic 的內部類
	public class BaseClassInnerClass{}
		
	// public 的列舉
	public enum BaseClassMemberEnum{}
	
}

複製程式碼
package com.byron4j.hightLevel.reflection;

public class ConcreteClass extends BaseClass implements BaseInterface{

	public int publicInt;
	private String privateString="private string";
	protected boolean protectedBoolean;
	Object defaultObject;
	
	public ConcreteClass(int i){
		this.publicInt=i;
	}

	@Override
	public void method1() {
		System.out.println("Method1 impl.");
	}

	@Override
	public int method2(String str) {
		System.out.println("Method2 impl.");
		return 0;
	}
	
	@Override
	public int method4(){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	public int method5(int i){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	// inner classes
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	//member enum
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	//member interface
	public interface ConcreteClassPublicInterface{}

	
}

複製程式碼

下面來看看使用反射的常用方法。

獲得Class物件

我們可以通過三種方式獲取物件的Class例項:

  • 通過靜態變數class

  • 使用示例的getClass()方法

  • java.lang.Class.forName(String 完整的類名),完整的類名包含包名。

原始型別的class、包裝型別的TYPE均可以獲得Class物件。

package com.byron4j.hightLevel.reflection;

public class ReflectionDemo {
	public static void main(String[] args) throws Exception{
		
		//方式一: 通過累的靜態變數class獲取Class物件
		Class concreteClass = ConcreteClass.class;
		
		//方式二:通過例項的getClass()方法獲取Class物件
		concreteClass = new ConcreteClass(7).getClass();
		
		//方式三:
		concreteClass = Class.forName("com.byron4j.hightLevel.reflection.ConcreteClass");
		
		//列印類相關資訊
		System.out.println(concreteClass.getCanonicalName());
		System.out.println(concreteClass.getName());
		
		
		/*++++++++++++++++++++++++++++++++++++++++++++++++
		 * 原始型別的class、包裝型別的TYPE
		 * +++++++++++++++++++++++++++++++++++++++++++++++
		 */
		
		Class primative = boolean.class;
		System.out.println(primative.getCanonicalName());
		
		Class doubleClass = Double.TYPE;
		System.out.println(doubleClass.getName());
		
		//陣列型別的class示例
		Class<?> arrayClass = Class.forName("[D");
		System.out.println(arrayClass.getCanonicalName());
		arrayClass = Class.forName("[B");
		System.out.println(arrayClass.getCanonicalName());
		arrayClass = Class.forName("[S");
		System.out.println(arrayClass.getCanonicalName());
		arrayClass = Class.forName("[C");
		System.out.println(arrayClass.getCanonicalName());
		arrayClass = Class.forName("[F");
		System.out.println(arrayClass.getCanonicalName());
		
		
		
	}
}

複製程式碼

輸出如下所示:

com.byron4j.hightLevel.reflection.ConcreteClass
com.byron4j.hightLevel.reflection.ConcreteClass
boolean
double
double[]
byte[]
short[]
char[]
float[]

複製程式碼

Class的getCanonicalName()方法返回類的名稱。在泛型中使用 java.lang.Class,可以幫助框架獲取子類。

獲取超類Super Class

getSuperclass() 方法,返回類的超類(基類、父類)的class例項,如果該類是java.lang.Object、原始型別、介面則返回null。如果該class是陣列形式,則該方法返回java.lang.Object。

Class<?> superClass = Class.forName("com.byron4j.hightLevel.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); 
System.out.println(Object.class.getSuperclass());
System.out.println(String[][].class.getSuperclass());
複製程式碼

輸入如下:

class com.byron4j.hightLevel.reflection.BaseClass
null
class java.lang.Object
複製程式碼

獲取公有的class

Class的getClasses() 方法可以獲取class的所有繼承的超類、介面和自己定義的公有類、介面、列舉等的陣列形式。

Class[] classARR = concreteClass.getClasses();
System.out.println(Arrays.toString(classARR));
複製程式碼

輸出:

[class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPublicClass, 
class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPublicEnum, 
interface com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPublicInterface, 
class com.byron4j.hightLevel.reflection.BaseClass$BaseClassInnerClass, 
class com.byron4j.hightLevel.reflection.BaseClass$BaseClassMemberEnum]
複製程式碼

獲取自身宣告的類

getDeclaredClasses()獲取當前型別自身定義的所有類、介面,並不包含從父類繼承過來的來、介面。

Class[] declareClassARR = concreteClass.getDeclaredClasses();
System.out.println("Arrays.toString(declareClassARR));
複製程式碼

輸出:

[class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassDefaultClass, 
class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPrivateClass, 
class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassProtectedClass, 
class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPublicClass, 
class com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPublicEnum,
interface com.byron4j.hightLevel.reflection.ConcreteClass$ConcreteClassPublicInterface]

複製程式碼

獲取定義該class的類

class.getDeclaringClass()獲取定義class的類。如果該類不是任何類或介面的成員,則返回null。

/**================================================
		 * getDeclaringClass
		 * ================================================
		 */
		System.out.println(BaseClassInnerClass.class.getDeclaringClass());
System.out.println(Double.TYPE.getDeclaringClass());
複製程式碼

該類BaseClassInnerClass是在BaseClass中定義的,於是輸出:

class com.byron4j.hightLevel.reflection.BaseClass

null
複製程式碼

獲取包名

getPackage() 方法獲取包的class例項。

/*===========================================
* getPackage()
* ==========================================
*/
System.out.println(concreteClass.getPackage().getName());
複製程式碼

輸出:

com.byron4j.hightLevel.reflection
複製程式碼

獲取類的修飾符

getModifiers()方法可以獲取class例項的訪問修飾符的個數。java.lang.reflect.Modifier.toString()可以獲取class的修飾符的字串形式。

/*===========================================
		 * getModifiers()、Modifier.toString()
		 * ==========================================
		 */
		System.out.println(concreteClass.getModifiers());
		System.out.println(Modifier.toString(concreteClass.getModifiers()));
複製程式碼

輸出:

1
public
複製程式碼

獲取型別引數

getTypeParameters()方法獲取class的型別宣告引數,如果有的話。比如集合框架的介面均制定了泛型。

Arrays.asList(Class.forName("java.util.Map").getTypeParameters()).forEach(
				s -> { System.out.println(s); }
		);
複製程式碼

輸出:

K V

獲取class實現的介面

getGenericInterfaces() 可以獲取class已經實現的介面的陣列形式,幷包含泛型介面。 getInterfaces()方法會返回所有實現的介面,但是不包含泛型介面。

/**=============================================
		 * getGenericInterfaces()、getInterfaces()
		 * =============================================
		 */
		Arrays.asList(concreteClass.getInterfaces()).forEach(
				s -> { System.out.println("com.byron4j.hightLevel.reflection.ConcreteClass實現的介面:" + s); }
		);
		System.out.println("========================================");
		Arrays.asList(concreteClass.getGenericInterfaces()).forEach(
				s -> { System.out.println("com.byron4j.hightLevel.reflection.ConcreteClass實現的介面:" + s); }
		);
		
		System.out.println("========================================");
		System.out.println("========================================");
		Arrays.asList(Class.forName("java.util.ArrayList").getInterfaces()).forEach(
				s -> { System.out.println("java.util.ArrayList實現的介面:" + s); }
		);
		System.out.println("========================================");
		Arrays.asList(Class.forName("java.util.ArrayList").getGenericInterfaces()).forEach(
				s -> { System.out.println("java.util.ArrayList實現的介面:" + s); }
		);
		
複製程式碼

輸出:

com.byron4j.hightLevel.reflection.ConcreteClass實現的介面:interface com.byron4j.hightLevel.reflection.BaseInterface
========================================
com.byron4j.hightLevel.reflection.ConcreteClass實現的介面:interface com.byron4j.hightLevel.reflection.BaseInterface
========================================
========================================
java.util.ArrayList實現的介面:interface java.util.List
java.util.ArrayList實現的介面:interface java.util.RandomAccess
java.util.ArrayList實現的介面:interface java.lang.Cloneable
java.util.ArrayList實現的介面:interface java.io.Serializable
========================================
java.util.ArrayList實現的介面:java.util.List<E>
java.util.ArrayList實現的介面:interface java.util.RandomAccess
java.util.ArrayList實現的介面:interface java.lang.Cloneable
java.util.ArrayList實現的介面:interface java.io.Serializable

複製程式碼

獲取所有的public方法

getMethods()方法可以獲取所有的public方法,包含父類、介面中繼承來的public方法。

/**=============================================
		 * getMethods()
		 * =============================================
		 */
		System.out.println("========================================");
		Arrays.asList(concreteClass.getMethods()).forEach(
				s -> { System.out.println("public型別的方法:" + s); }
		);
複製程式碼

輸出:

public型別的方法:public void com.byron4j.hightLevel.reflection.ConcreteClass.method1()
public型別的方法:public int com.byron4j.hightLevel.reflection.ConcreteClass.method2(java.lang.String)
public型別的方法:public int com.byron4j.hightLevel.reflection.ConcreteClass.method4()
public型別的方法:public int com.byron4j.hightLevel.reflection.ConcreteClass.method5(int)
public型別的方法:public static int com.byron4j.hightLevel.reflection.BaseClass.method5()
public型別的方法:public final void java.lang.Object.wait() throws java.lang.InterruptedException
public型別的方法:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public型別的方法:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public型別的方法:public boolean java.lang.Object.equals(java.lang.Object)
public型別的方法:public java.lang.String java.lang.Object.toString()
public型別的方法:public native int java.lang.Object.hashCode()
public型別的方法:public final native java.lang.Class java.lang.Object.getClass()
public型別的方法:public final native void java.lang.Object.notify()
public型別的方法:public final native void java.lang.Object.notifyAll()

複製程式碼

獲取class的所有public構造器

getConstructors()方法能夠獲取所有的public型別構造器

/**=============================================
		 * getConstructors()
		 * =============================================
		 */
		System.out.println("========================================");
		Arrays.asList(concreteClass.getConstructors()).forEach(
				s -> { System.out.println("public型別的構造器:" + s); }
		);
複製程式碼

輸出:

public型別的構造器:public com.byron4j.hightLevel.reflection.ConcreteClass(int)

獲取所有的public屬性(成員變數)

getFields()方法可以獲取所有的public屬性。包含父類、介面中的屬性。

/**=============================================
		 * getFields()
		 * =============================================
		 */
		System.out.println("========================================");
		Arrays.asList(concreteClass.getFields()).forEach(
				s -> { System.out.println("public型別的屬性:" + s); }
		);
複製程式碼

輸出:

public型別的屬性:public int com.byron4j.hightLevel.reflection.ConcreteClass.publicInt
public型別的屬性:public static final int com.byron4j.hightLevel.reflection.BaseInterface.interfaceInt
public型別的屬性:public int com.byron4j.hightLevel.reflection.BaseClass.baseInt

複製程式碼

獲取所有的註解

getAnnotations()方法可以獲取所有的註解。但是隻有保留策略為RUNTIME的註解。

我們給 類加上註解@Deprecated。

@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface{...}

/////////////////////////////////////
/**=============================================
		 * getAnnotations()
		 * =============================================
		 */
		System.out.println("========================================");
		Arrays.asList(concreteClass.getAnnotations()).forEach(
				s -> { System.out.println("註解:" + s); }
		);
複製程式碼

輸出:

註解:@java.lang.Deprecated()

反射在屬性(成員變數)中的應用

反射API提供了幾個方法可以在執行時分解類的成員變數以及設定其值。

獲取public屬性

除了前面的getFields()方法能獲取全部public屬性之外,還提供了一個獲取指定屬性名稱的方法getField()。這個方法會在該class-->介面-->父類的順序尋找指定的屬性。

場景一:我們在ConcreteClass類、BaseClass類、BaseInterface介面分別增加一個屬性: public String name = "currClass---NAME";

public String name = "superClass---NAME";

public String name = "superInterface---NAME";


System.out.println(concreteClass.getField("name"));

輸出為:
public java.lang.String com.byron4j.hightLevel.reflection.ConcreteClass.name


複製程式碼

場景二:我們在BaseClass類、BaseInterface介面分別增加一個屬性: public String name = "superClass---NAME";

public String name = "superInterface---NAME";

這時候獲取的屬性name是介面中的屬性:

public static final java.lang.String com.byron4j.hightLevel.reflection.BaseInterface.name

場景三:我們僅僅在BaseClass類增加一個屬性: public String name = "superClass---NAME";

這時候獲取的屬性name是父類中的屬性:

public java.lang.String com.byron4j.hightLevel.reflection.BaseClass.name

如果是獲取不存在的屬性,則出現異常:

System.out.println(concreteClass.getField("hello"));

輸出:

java.lang.NoSuchFieldException: hello

獲取宣告屬性的型別

try {
			System.out.println( concreteClass.getField("interfaceInt").getDeclaringClass() );
		} catch (NoSuchFieldException | SecurityException e) {
			
			e.printStackTrace();
		}
複製程式碼

輸出:

interface com.byron4j.hightLevel.reflection.BaseInterface

獲取屬性的型別

getType()方法返回屬性的型別的class例項。

System.out.println(concreteClass.getField("interfaceInt").getType().getCanonicalName());
複製程式碼

輸出:

int

Get/Set public型別的屬性值

Field.get(Object) 獲取該屬性的值。

Field field = concreteClass.getField("publicInt");
System.out.println("屬性的型別:"+field.getType());

ConcreteClass obj= new ConcreteClass(7);

System.out.println("獲取屬性值:" + field.get(obj));

field.set(obj, 77);
System.out.println("獲取屬性值:" + field.get(obj));
複製程式碼

輸出:

屬性的型別:int 獲取屬性值:7 獲取屬性值:77

Field.get()返回的是一個Object型別,如果是原始型別則返回其包裝型別。如果是final屬性,set() 方法丟擲java.lang.IllegalAccessException

Get/Set private型別的屬性值

java中在類之外是不能訪問private變數的。但是通過反射可以關閉檢查訪問修飾符的機制。

Field privateField = concreteClass.getDeclaredField("privateString");
		
System.out.println(privateField.get(obj));
複製程式碼

輸出,不能訪問private的屬性:

java.lang.IllegalAccessException: Class com.byron4j.hightLevel.reflection.ReflectionDemo2 can not access a member of class com.byron4j.hightLevel.reflection.ConcreteClass with modifiers "private"

設定可訪問機制Field.setAccessible(true);

		Field privateField = concreteClass.getDeclaredField("privateString");
		privateField.setAccessible(true);
		System.out.println(privateField.get(obj));

		privateField.set(obj, "新的私有屬性值[value]");
		System.out.println(privateField.get(obj));
複製程式碼

輸出:

private string 新的私有屬性值[value]

與方法相關的反射方法

使用反射技術可以獲得方法的資訊以及呼叫執行它。我們來學習獲得方法、呼叫方法並訪問私有方法。

獲得public方法

我們可以使用 getMethod()方法獲的public class的方法,需要提供方法的名稱、引數型別。如果class找不到指定的方法,則會繼續向上從其父類中查詢。

下面我們以一個獲取HashMap 的put方法的例子來展示如何方法的引數型別、方法訪問修飾符和返回型別。

/*=========================================
		 * 方法
		 * ========================================
		 */
		
		Class mapClass = HashMap.class;
		Method mapPut = mapClass.getMethod("put", Object.class, Object.class);
		//獲取引數型別
		System.out.println(Arrays.toString(mapPut.getParameterTypes()));
	
		//獲取返回型別
		System.out.println(mapPut.getReturnType());
		
		//訪問修飾符
		System.out.println(Modifier.toString(mapPut.getModifiers()));
複製程式碼

輸出:

[class java.lang.Object, class java.lang.Object] class java.lang.Object public

呼叫public方法

可以利用Method.invoke() 方法呼叫指定的方法。

//呼叫方法
Map<String, String> map = new HashMap<String, String>();
mapPut.invoke(map, "key", "val");
System.out.println(map);
複製程式碼

輸出:

{key=val}

呼叫private方法

我們可以使用getDeclaredMethod()方法獲取私有方法,然後關閉訪問限制,即可呼叫。

/**
* 訪問私有方法
*/
Method method3 = BaseClass.class.getDeclaredMethod("method3", null);

method3.setAccessible(true);
//靜態方法的呼叫物件可以傳入null
method3.invoke(null, null);
複製程式碼

輸出:

Method3

反射在構造器中的使用

我們可以使用getConstructor()方法獲取指定的public構造器。

/**
* 反射在構造器中的使用
*/
Constructor<?> constructor = ConcreteClass.class.getConstructor(int.class);

System.out.println(Arrays.toString(constructor.getParameterTypes())); 
	
Constructor<?> hashMapConstructor = HashMap.class.getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); 
		
複製程式碼

輸出:

[int] []

利用構造器初始化物件例項

我們可以利用constructor 例項的newInstance() 方法獲初始化例項。

/**
* 初始化例項
*/

Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

HashMap<String,String> myMap = (HashMap<String,String>)hashMapConstructor.newInstance(null);
複製程式碼

相關文章