Java 反射示例教程

Java反射提供了检查和修改应用程序运行行为的能力。在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 对象上返回类的超类。如果此 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 对象表示原始类型、数组类或 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()方法返回类修饰符的int表示,我们可以使用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();
//打印 "[java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//打印 "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));		

获取所有公共方法

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

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

获取所有公共构造函数

getConstructors() 方法返回对象的类引用的公共构造函数列表。

//获取所有公共构造函数
Constructor[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//打印 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提供了几种方法来分析类字段并在运行时修改它们的值,在本节中,我们将介绍一些常用的反射函数用于方法。

获取公共字段

在上一节中,我们看到了如何获取类的所有公共字段的列表。反射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