Java 反射示例教程

Java Reflection 提供了檢查和修改應用程式運行時行為的能力。在 Java 中,反射是核心 Java 的一個高級主題。使用 Java 反射,我們可以在運行時檢查類、介面列舉,甚至獲取它們的結構、方法和字段信息,即使類在編譯時不可訪問。我們還可以使用反射來實例化對象、調用其方法、更改字段值。

Java 反射

  1. Java 中的反射
  2. Java反射用於類
  3. 用於字段的Java反射
  4. 用於方法的Java反射
  5. 用於構造函數的Java反射
  6. Java反射用於註釋

  1. Java中的反射

Java中的反射是一個非常強大的概念,它在普通編程中用處不大,但它是大多數Java、J2EE框架的基礎。一些使用Java反射的框架包括:

  1. JUnit – 使用反射解析@Test註釋以獲取測試方法,然後調用它。
  2. Spring – 依賴注入,詳情請參閱Spring依賴注入
  3. Tomcat Web容器通過解析web.xml文件和請求URI將請求轉發到正確的模塊。
  4. Eclipse 方法名的自動完成
  5. Struts
  6. Hibernate

列表是無窮盡的,它們都使用Java反射,因為所有這些框架都對用戶定義的類、接口、它們的方法等一無所知,也無法訪問。在我們已經可以訪問類和接口的正常編程中,我們不應該使用反射,因為它存在以下缺點。

  • 性能差 – 由於Java反射動態解析類型,涉及處理,如掃描類路徑以查找要加載的類,導致性能慢。
  • 安全限制 – 反射需要運行時權限,可能在運行在安全管理器下的系統中不可用。這可能導致應用程序在運行時由於安全管理器的原因而失敗。
  • 安全問題 – 使用反射,我們可以訪問不應該訪問的代碼部分,例如,我們可以訪問類的私有字段並更改其值。這可能是一個嚴重的安全威脅,並使應用程序行為異常。
  • 維護困難 – 反射代碼難以理解和調試,同時代碼的任何問題在編譯時無法找到,因為類可能不存在,使其不夠靈活且難以維護。
  1. Java 類別的反射

在 Java 中,每個物件都是基本類型或參照。所有的類別、列舉、陣列都是參照類型,並繼承自 java.lang.Object。基本類型包括 – boolean、byte、short、int、long、char、float 和 double。 java.lang.Class 是所有反射操作的入口點。對於每種物件類型,JVM 實例化一個 不可變java.lang.Class 實例,提供方法來檢查物件的運行時特性、創建新物件、調用其方法以及取得/設定物件字段。在本節中,我們將探討 Class 的重要方法,為方便起見,我創建了一些具有 繼承 階層的類別和介面。

package com.journaldev.reflection;

public interface BaseInterface {
	
	public int interfaceInt=0;
	
	void method1();
	
	int method2(String str);
}
package com.journaldev.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");
	}
	
	// 內部公共類別
	public class BaseClassInnerClass{}
		
	// 成員公共列舉
	public enum BaseClassMemberEnum{}
}
package com.journaldev.reflection;

@Deprecated
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;
	}
	
	// 內部類別
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	// 成員列舉
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	// 成員介面
	public interface ConcreteClassPublicInterface{}

}

讓我們看一些關於類別的重要反射方法。

獲取類別對象

我們可以使用三種方法獲取對象的類別 – 通過靜態變數class、使用對象的getClass()方法以及java.lang.Class.forName(String fullyClassifiedClassName)。對於基本類型和數組,我們可以使用靜態變數class。包裝類提供另一個靜態變數TYPE以獲取類別。

// 使用反射獲取類別
Class concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// 下面的方法在像JUnit這樣的框架中大多數情況下被使用
	//Spring依賴注入、Tomcat Web容器
	//Eclipse自動完成方法名、Hibernate、Struts2等等
	//因為ConcreteClass在編譯時不可用
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//對於基本類型、包裝類和數組
Class booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean

Class cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double

Class cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]

Class twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]

getCanonicalName() 返回底層類別的規範名稱。請注意,java.lang.Class 使用泛型,它有助於框架確保檢索到的類別是框架基類的子類別。查看Java 泛型教程以了解泛型及其通配符。

獲取超級類別

getSuperclass() 在類別物件上的方法返回該類別的超級類別。如果這個 Class 代表 Object 類別、一個介面、一個基本類型或 void,則返回 null。如果此物件代表一個陣列類別,則返回代表 Object 類別的 Class 物件。

Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"

獲取公共成員類別

getClasses() 方法返回一個陣列,其中包含代表該 Class 物件所代表的類別中的所有公共類別、介面和列舉型態的 Class 物件。這包括從父類別繼承的公共類別和介面成員,以及該類別所聲明的公共類別和介面成員。如果該 Class 物件沒有公共成員類別或介面,或者該 Class 物件代表原始類型、陣列類別或 void,則此方法返回長度為 0 的陣列。

Class[] classes = concreteClass.getClasses();
//[class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.journaldev.reflection.BaseClass$BaseClassInnerClass, 
//class com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

取得宣告的類別

getDeclaredClasses() 方法返回一個 Class 物件的陣列,反映了所有聲明為該 Class 物件所代表的類別的成員的類別和介面。返回的陣列不包括在繼承類別和介面中聲明的類別。

//获取所有在ConcreteClass中显式声明的类、接口和枚举
Class[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//打印[class com.journaldev.reflection.ConcreteClass $ ConcreteClassDefaultClass, 
//class com.journaldev.reflection.ConcreteClass $ ConcreteClassDefaultEnum, 
//class com.journaldev.reflection.ConcreteClass $ ConcreteClassPrivateClass, 
//class com.journaldev.reflection.ConcreteClass $ ConcreteClassProtectedClass, 
//class com.journaldev.reflection.ConcreteClass $ ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass $ ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass $ ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

获取声明类

getDeclaringClass()方法返回代表声明它的类的Class对象。

Class innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//打印com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

获取包名

getPackage()方法返回此类的包。此类的类加载器用于查找包。我们可以调用Package的getName()方法来获取包的名称。

//打印 "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());

獲取類修飾符

getModifiers() 方法返回類修飾符的整數表示形式,我們可以使用 java.lang.reflect.Modifier.toString() 方法將其以源代碼中使用的字符串格式獲取。

System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//打印 "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

獲取類型參數

getTypeParameters() 如果與該類關聯的有任何類型參數,則返回 TypeVariable 數組。類型參數按聲明的順序返回。

//獲取類型參數(泛型)
TypeVariable[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable t : typeParameters)
System.out.print(t.getName()+",");

獲取已實現的接口

getGenericInterfaces() 方法返回帶有泛型類型信息的類實現的接口數組。我們還可以使用 getInterfaces() 來獲取所有已實現接口的類表示。

Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//prints "[java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));		

獲取所有公共方法

getMethods()方法返回Class的公共方法数组,包括其超类和超接口的公共方法。

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//prints ConcreteClass、BaseClass、Object的公共方法
System.out.println(Arrays.toString(publicMethods));

獲取所有公共構造函數

getConstructors()方法返回對象的類引用的公共構造函數列表。

//獲取所有公共構造函數
Constructor[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//prints ConcreteClass的公共構造函數
System.out.println(Arrays.toString(publicConstructors));

獲取所有公共字段

getFields()方法返回類的公共字段數組,包括其超類和超接口的公共字段。

//獲取所有公共字段
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//打印ConcreteClass、其父類和超級接口的公共字段
System.out.println(Arrays.toString(publicFields));

獲取所有註解

getAnnotations() 方法返回元素的所有註解,我們可以將其用於類、字段和方法。請注意,僅具有 RUNTIME 保留策略的反射可用註解,請查看 Java 註解教程。我們將在後面的部分更詳細地研究這一點。

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//打印[@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
  1. 用於字段的Java反射

反射API提供了幾種方法來分析類字段並在運行時修改其值,在本節中,我們將研究一些常用的反射方法。

獲取公共字段

在上一節中,我們看到如何獲取類的所有公共字段列表。Reflection API 還提供了通過 getField() 方法獲取類的特定公共字段的方法。該方法在指定的類引用中查找字段,然後在超級接口中查找,然後在超級類中查找。

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");

上述調用將從由 ConcreteClass 實現的 BaseInterface 返回字段。如果找不到字段,則會拋出 NoSuchFieldException。

字段聲明類

我們可以使用字段對象的 getDeclaringClass() 來獲取聲明字段的類。

try {
	Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
	Class<?> fieldClass = field.getDeclaringClass();
	System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
	e.printStackTrace();
}

獲取字段類型

getType() 方法返回聲明字段類型的 Class 對象,如果字段是原始類型,則返回包裝類對象。

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int			

獲取/設置公共字段值

我們可以使用反射來獲取並設置對象中字段的值。

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10

get() 方法返回對象,因此如果字段是基本類型,它將返回相應的包裝器類。如果字段是靜態的,我們可以在 get() 方法中將對象傳遞為 null。有幾種 set*() 方法可以將對象設置為字段,或將不同類型的基本類型設置為字段。我們可以獲取字段的類型,然後調用正確的函數來正確設置字段值。如果字段是 final 的,則 set() 方法將拋出 java.lang.IllegalAccessException。

獲取/設置私有字段值

我們知道私有字段和方法無法在類外部訪問,但是使用反射,我們可以通過關閉字段修飾符的 java 訪問檢查來獲取/設置私有字段值。

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//使用下面的方法調用關閉訪問檢查
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"
  1. Java 反射方法

使用反射,我們可以獲取有關方法的信息,並且還可以調用它。在本節中,我們將學習不同的方法來獲取方法、調用方法和訪問私有方法。

獲取公共方法

我們可以使用 getMethod() 來獲取類的公共方法,我們需要傳遞方法名和方法的參數類型。如果在類中找不到該方法,反射 API 會在超類中查找該方法。在下面的示例中,我使用反射獲取了 HashMap 的 put() 方法。該示例還展示了如何獲取方法的參數類型、方法修飾符和返回類型。

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
// 獲取方法的參數類型,打印 "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
// 獲取方法的返回類型,返回 "class java.lang.Object",void 的類引用
System.out.println(method.getReturnType());
// 獲取方法的修飾符
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

調用公共方法

我們可以使用 Method 對象的 invoke() 方法來調用方法,在下面的示例代碼中,我使用反射來調用 HashMap 上的 put 方法。

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}

如果方法是靜態的,我們可以將 NULL 作為對象參數傳遞。

調用私有方法

我們可以使用 getDeclaredMethod() 來獲取私有方法,然後關閉訪問權限檢查以調用它,下面的示例展示了如何調用 BaseClass 的 method3(),該方法是靜態的並且沒有參數。

// 調用私有方法
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
  1. Java 反射構造函數

Reflection API 提供了分析类的构造函数并通过调用构造函数创建类的新实例的方法。我们已经学习了如何获取所有公共构造函数。

获取公共构造函数

我们可以在对象的类表示上使用getConstructor()方法来获取特定的公共构造函数。下面的示例演示了如何获取上面定义的ConcreteClass的构造函数以及HashMap的无参数构造函数。它还展示了如何获取构造函数的参数类型数组。

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//获取构造函数参数
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Constructor hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"

使用构造函数实例化对象

我们可以在构造函数对象上使用newInstance()方法来实例化类的新实例。由于我们在编译时没有类的信息时使用反射,因此我们可以将它分配给Object,然后进一步使用反射来访问它的字段并调用它的方法。

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//获取构造函数参数
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

Constructor hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap myMap = (HashMap) hashMapConstructor.newInstance(null);
  1. 反思注解

注解是在Java 1.5中引入的,用于提供有关类、方法或字段的元数据信息,现在在诸如Spring和Hibernate等框架中广泛使用。反射API也得到了扩展,以在运行时支持分析注解。使用反射API,我们可以分析保留策略为Runtime的注解。我已经撰写了关于注解以及如何使用反射API解析注解的详细教程,建议您查看Java注解教程。这就是关于Java反射示例教程的全部内容,希望您喜欢这个教程并理解Java反射API的重要性。

Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial