Java反射示例教程

Java反射提供了检查和修改应用程序运行行为的能力。Java中的反射是核心Java中的一个高级主题。使用Java反射,我们可以在运行时检查类、接口、枚举,获取它们的结构、方法和字段信息,即使在编译时类不可访问。我们还可以使用反射来实例化对象,调用其方法,更改字段值。

Java 反射

    在Java中的反射
    Java类的反射

    获取类对象
    获取父类
    获取公共成员类
    获取声明的类
    获取声明类
    获取包名
    获取类修饰符
    获取类型参数
    获取实现的接口
    获取所有公共方法
    获取所有公共构造函数
    获取所有公共字段
    获取所有注释

    Java字段的反射

    获取公共字段
    字段声明类
    获取字段类型
    获取/设置公共字段值
    获取/设置私有字段值

    Java方法的反射

    获取公共方法
    调用公共方法
    调用私有方法

    Java构造函数的反射

    获取公共构造函数
    使用构造函数实例化对象

    Java注释的反射

    Java中的反射

Java中的反射是一个非常强大的概念,在正常的编程中用处不大,但它是大多数Java、J2EE框架的基础。一些使用Java反射的框架包括:

    JUnit – 使用反射来解析@Test注解以获取测试方法并进行调用。
    Spring – 依赖注入,请阅读更多关于Spring依赖注入的内容。
    Tomcat Web容器通过解析web.xml文件和请求URI将请求转发到正确的模块。
    Eclipse自动完成方法名称。
    Struts
    Hibernate

列表是无止境的,这些框架都使用 Java 反射,因为它们都无法获取和了解用户定义的类、接口及其方法等。在我们已经可以访问这些类和接口的情况下,我们不应该在正常编程中使用反射,因为存在以下缺点。

  • Poor Performance – Since java reflection resolve the types dynamically, it involves processing like scanning the classpath to find the class to load, causing slow performance.
  • Security Restrictions – Reflection requires runtime permissions that might not be available for system running under security manager. This can cause you application to fail at runtime because of security manager.
  • Security Issues – Using reflection we can access part of code that we are not supposed to access, for example we can access private fields of a class and change it’s value. This can be a serious security threat and cause your application to behave abnormally.
  • High Maintenance – Reflection code is hard to understand and debug, also any issues with the code can’t be found at compile time because the classes might not be available, making it less flexible and hard to maintain.
    Java反射类的用途

在Java中,每个对象都是原始类型或引用类型。所有的类、枚举和数组都是引用类型,并继承自java.lang.Object。原始类型包括布尔型、字节型、短整型、整型、长整型、字符型、浮点型和双精度型。java.lang.Class是所有反射操作的入口点。对于每种对象类型,JVM会实例化一个不可变的java.lang.Class实例,以提供方法来检查对象的运行时属性,创建新对象,调用其方法,以及获取/设置对象字段。在本节中,我们将讨论Class的一些重要方法,为了方便起见,我将创建一些具有继承层次结构的类和接口。

package com.Olivia.reflection;

public interface BaseInterface {
	
	public int interfaceInt=0;
	
	void method1();
	
	int method2(String str);
}
package com.Olivia.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");
	}
	
	// inner public class
	public class BaseClassInnerClass{}
		
	//member public enum
	public enum BaseClassMemberEnum{}
}
package com.Olivia.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;
	}
	
	// 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{}

}

让我们来看一些类的重要反射方法。

获取类对象

我们可以使用三种方法来获取一个对象的类 – 通过静态变量类、使用对象的getClass()方法以及java.lang.Class.forName(String fullyClassifiedClassName)。对于原始类型和数组,我们可以使用静态变量类。封装类提供了另一个静态变量TYPE来获取类。

// Get Class using reflection
Class<?> concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// below method is used most of the times in frameworks like JUnit
	//Spring dependency injection, Tomcat web container
	//Eclipse auto completion of method names, hibernate, Struts2 etc.
	//because ConcreteClass is not available at compile time
	concreteClass = Class.forName("com.Olivia.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.Olivia.reflection.ConcreteClass

//for primitive types, wrapper classes and arrays
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泛型教程以了解泛型及其通配符用法。

取得超类

在Class对象上调用getSuperclass()方法会返回该类的父类。如果这个Class代表的是Object类、一个接口、原始类型或者void,则返回null。如果这个对象代表的是一个数组类,则返回代表Object类的Class对象。

Class<?> superClass = Class.forName("com.Olivia.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.Olivia.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.Olivia.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.Olivia.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.Olivia.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.Olivia.reflection.BaseClass$BaseClassInnerClass, 
//class com.Olivia.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

获取声明的类

getDeclaredClasses()方法返回一个数组,其中包含反映由此Class对象表示的类的所有成员声明的类和接口的Class对象。返回的数组不包括在继承的类和接口中声明的类。

//getting all of the classes, interfaces, and enums that are explicitly declared in ConcreteClass
Class<?>[] explicitClasses = Class.forName("com.Olivia.reflection.ConcreteClass").getDeclaredClasses();
//prints [class com.Olivia.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//class com.Olivia.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//class com.Olivia.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//class com.Olivia.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//class com.Olivia.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.Olivia.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.Olivia.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

获取声明类

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

Class<?> innerClass = Class.forName("com.Olivia.reflection.ConcreteClass$ConcreteClassDefaultClass");
//prints com.Olivia.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

获取软件包名称

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

//prints "com.Olivia.reflection"
System.out.println(Class.forName("com.Olivia.reflection.BaseInterface").getPackage().getName());

获取类的修饰符

getModifiers()方法返回类修饰符的整数表示形式,我们可以使用java.lang.reflect.Modifier.toString()方法以字符串格式获取源代码中使用的修饰符。

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

获取类型参数

如果类与任何类型参数相关联,则getTypeParameters()会返回TypeVariable数组。这些类型参数以声明的相同顺序返回。

//Get Type parameters (generics)
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<K, V>, 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.Olivia.reflection.ConcreteClass").getMethods();
//prints public methods of ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

获取所有公共构造函数

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

//Get All public constructors
Constructor<?>[] publicConstructors = Class.forName("com.Olivia.reflection.ConcreteClass").getConstructors();
//prints public constructors of ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

获取所有公共字段

getFields()方法返回类的公共字段数组,包括其父类和父接口的公共字段。

//Get All public fields
Field[] publicFields = Class.forName("com.Olivia.reflection.ConcreteClass").getFields();
//prints public fields of ConcreteClass, it's superclass and super interfaces
System.out.println(Arrays.toString(publicFields));

获取所有的注释

getAnnotations()方法返回元素的所有注解,我们可以用它与类、字段和方法一起使用。注意,只有使用反射机制进行运行时保留策略的注解才可用,请查看Java注解教程。我们将在后面的章节中详细讨论这个问题。

java.lang.annotation.Annotation[] annotations = Class.forName("com.Olivia.reflection.ConcreteClass").getAnnotations();
//prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
    Java反射用于字段

反射API提供了几种方法来分析类的字段,并在运行时修改它们的值。在本节中,我们将介绍一些常用的反射方法,用于处理方法。

获取公共字段

在上一部分中,我们看到了如何获取一个类的所有公共字段的列表。反射API还提供了通过getField()方法获取特定公共字段的方法。该方法会在指定的类引用中查找该字段,然后在超级接口和超级类中查找。

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

如果在ConcreteClass中实现了BaseInterface接口,以上调用将返回BaseInterface中的字段。如果未找到该字段,则会抛出NoSuchFieldException异常。

声明字段的类

我们可以使用字段对象的getDeclaringClass()方法来获取声明该字段的类。

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

获取字段类型

如果字段是原始数据类型,getType()方法返回声明字段类型的Class对象,如果字段是包装类对象,则返回包装类对象。

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

获取/设置公共字段的值

我们可以通过反射来获取和设置对象中字段的值。

Field field = Class.forName("com.Olivia.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()方法返回Object类型的对象,所以如果字段是原始类型,它会返回对应的包装类。如果字段是静态的,在get()方法中我们可以传递null作为参数。有几个set*()方法可以将对象设置到字段中,或者将不同类型的原始类型设置到字段中。我们可以获取字段的类型,然后调用正确的函数来正确设置字段的值。如果字段是final的,set()方法会抛出java.lang.IllegalAccessException异常。

设置/获取私有字段的值。

我们知道,私有字段和方法在类外部是无法访问的,但是通过使用反射,我们可以通过关闭Java对字段修饰符的访问检查来获取/设置私有字段的值。

Field privateField = Class.forName("com.Olivia.reflection.ConcreteClass").getDeclaredField("privateString");
//turning off access check with below method call
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"
    Java方法的反射

通过反射,我们可以获取有关方法的信息并执行它。在本节中,我们将学习不同的方法来获取一个方法,调用一个方法并访问私有方法。

获取公共方法

我们可以使用getMethod()方法来获取一个类的公共方法,我们需要传递方法的名称和参数类型。如果在类中找不到该方法,反射API会在父类中查找该方法。在下面的示例中,我使用反射来获取HashMap的put()方法。示例还展示了如何获取方法的参数类型、方法修饰符和返回类型。

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//get method parameter types, prints "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//get method return type, return "class java.lang.Object", class reference for void
System.out.println(method.getReturnType());
//get method modifiers
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()。

//invoking private method
Method method = Class.forName("com.Olivia.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
    构造函数的Java反射

反射API提供了获取类的构造函数进行分析的方法,我们可以通过调用构造函数来创建类的新实例。我们已经学习了如何获取所有的公共构造函数。

获取公共构造函数。

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

Constructor<?> constructor = Class.forName("com.Olivia.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
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.Olivia.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
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<String,String> myMap = (HashMap<String,String>) hashMapConstructor.newInstance(null);
    批注的反思

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