Java反射机制详解:动态访问和操作对象
目录
- 先看个例子
- 基本概念
- Class 2.1 获取Class 2.2 通过Class获取类修饰符和类型
- Member 3.1 Field 3.1.1 获取Field 3.1.2 获取变量类型、修饰符、注解 3.1.3 获取、设置变量值 3.2 Method 3.2.1 获取Method 3.2.2 获取方法返回类型 3.2.3 获取方法参数类型 3.2.4 获取方法声明抛出的异常类型 3.2.5 获取方法参数名称 3.2.6 通过反射调用方法 3.3 Constructor 3.3.1 获取构造方法 3.3.2 创建对象
- 数组和枚举 4.1 数组 4.2 枚举
- 反射的缺点 参考
1. 先看个例子
public class People {private People(int age) {}public void show() {System.out.println("test reflect");}
}//test code
Class<?> clazz = Class.forName("com.xfhy.ref.People");
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(int.class);
declaredConstructor.setAccessible(true);
People person = (People) declaredConstructor.newInstance(12);
person.show();
通过这个简单的例子,我们可以看到Java反射的强大之处:即使People的构造方法是私有的,我们仍然可以通过反射机制来创建其实例。
2. 基本概念
Java的反射机制能够:
- 对于任意一个类,都能够知道这个类的所有属性和方法。
- 对于任意一个对象,都能够调用它的任意一个方法和属性。
这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。反射机制主要涉及两个类:Class
和Member
。
3. Class
Class
是反射能够实现的基础,Class
是JDK提供的一个类,完整路径是java.lang.Class
。本质上这个类和Math
、String
或者自己定义的各种类没有区别。
3.1 获取Class
获取Class
对象的几种方法:
Object.getClass()
:对象实例调用getClass
方法。The.class
:类调用.class
,例如String.class
。Class.forName("")
:传入类的全路径。The.TYPE
:例如Double.TYPE
。Class.getSuperclass()
:某个实例调用getClass().getSuperclass()
。
3.2 通过Class获取类修饰符和类型
以HashMap
为例,我们可以通过反射获取类的修饰符、泛型信息、实现的接口、父类和注解等信息。
Class<?> clazz = HashMap.class;
System.out.println("Class : " + clazz.getCanonicalName());
System.out.println(Modifier.toString(clazz.getModifiers()));
TypeVariable<? extends Class<?>>[] typeParameters = clazz.getTypeParameters();
StringBuilder stringBuilder = new StringBuilder("Parameters : ");
for (TypeVariable<? extends Class<?>> typeParameter : typeParameters) {stringBuilder.append(typeParameter.getName()).append(" ");
}
System.out.println(stringBuilder.toString());Type[] genericInterfaces = clazz.getGenericInterfaces();
StringBuilder interfaces = new StringBuilder("Implemented Interfaces : ");
for (Type intf : genericInterfaces) {interfaces.append(intf.toString()).append(" ");
}
System.out.println(interfaces.toString());Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {System.out.println(superclass.getCanonicalName());
}Annotation[] annotations = clazz.getAnnotations();
StringBuilder annotation = new StringBuilder("Annotations : ");
for (Annotation a : annotations) {annotation.append(a.toString()).append(" ");
}
System.out.println(annotation.toString());
4. Member
Member
有三个实现类:
java.lang.reflect.Field
:类变量java.lang.reflect.Method
:类方法java.lang.reflect.Constructor
:类构造方法
4.1 Field
通过Field
可以访问给定类对象的类变量,包括获取变量的类型、修饰符、注解、变量名、变量值等,即使是private
的也是OK的。
4.1.1 获取Field
Class
提供了四种方法获得给定类的Field
:
getDeclaredField(String name)
:获取指定的变量,包括private
的。getField(String name)
:获取指定的变量,只能是public
的。getDeclaredFields()
:获取所有变量,包括private
的。getFields()
:获取所有变量,只能是public
的。
4.1.2 获取变量类型、修饰符、注解
Class clazz = People.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("变量名: ").append(field.getName()).append("\n");stringBuilder.append("变量类型: ").append(field.getType()).append("\n");stringBuilder.append("变量修饰符: ").append(Modifier.toString(field.getModifiers())).append("\n");Annotation[] annotations = field.getAnnotations();if (annotations.length != 0) {stringBuilder.append(" 变量注解 : ");for (Annotation a : annotations) {stringBuilder.append(a.toString()).append(" ");}}System.out.println(stringBuilder.toString());
}
4.1.3 获取、设置变量值
People people = new People("张三", 17);
Class<? extends People> peopleClass = people.getClass();
try {Field nameField = peopleClass.getDeclaredField("name");nameField.setAccessible(true);Field ageField = peopleClass.getField("age");String name = (String) nameField.get(people);int age = ageField.getInt(people);System.out.println("name = " + name + ", age = " + age);nameField.set(people, "李四");ageField.set(people, 18);System.out.println(people.toString());
} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();
}
4.2 Method
4.2.1 获取Method
Class
提供了四种方法获取Method
:
getDeclaredMethod(String name, Class<?>... parameterTypes)
getMethod(String name, Class<?>... parameterTypes)
getDeclaredMethods()
getMethods()
4.2.2 获取方法返回类型
getReturnType()
:获取目标方法返回类型对应的Class
对象。getGenericReturnType()
:获取目标方法返回类型对应的Type
对象。
4.2.3 获取方法参数类型
getParameterTypes()
:获取目标方法各参数类型对应的Class
对象。getGenericParameterTypes()
:获取目标方法各参数类型对应的Type
对象。
4.2.4 获取方法声明抛出的异常类型
getExceptionTypes()
:获取目标方法抛出的异常类型对应的Class
对象。getGenericExceptionTypes()
:获取目标方法抛出的异常类型对应的Type
对象。
4.2.5 获取方法参数名称
.class
文件中默认不存储方法参数名称,如果想要获取方法参数名称,需要在编译的时候加上-parameters
参数。
4.2.6 通过反射调用方法
通过Method
的invoke()
方法来反射调用目标方法,第一个参数为需要调用的目标类对象。如果方法是static
的,则该参数为null
,后面的参数是目标方法的参数值,顺序与目标方法声明中的参数顺序一致。
4.3 Constructor
4.3.1 获取构造方法
和Method
类似。
4.3.2 创建对象
一般使用java.lang.reflect.Constructor.newInstance()
来构建对象,另一种Class.newInstance()
已经废弃。
5. 数组和枚举
5.1 数组
数组类型:数组本质上是一个对象,所以它也有自己的类型。例如对于int[] intArray
,数组类型为class [I
。数组类中的[
个数代表数组的维度,例如[
代表一维数组,[[
代表二维数组,[
后面的字母代表数组元素类型,I
代表int
,一般为类型的首字母大写(long
类型例外,为J
)。
创建和初始化数组:
Object array = Array.newInstance(int.class, 2);
Array.set(array, 0, 1);
Array.set(array, 1, 2);
System.out.println(Array.get(array, 1));
多维数组:Java反射没有提供能够直接访问多维数组元素的API,但你可以把多维数组当成数组的数组处理。
5.2 枚举
枚举隐式继承自java.lang.Enum
,Enum
继承自Object
结论
Java反射机制是Java语言的一个重要特性,它提供了一种动态访问和操作对象的能力。虽然它有性能和安全上的考量,但在需要动态性和灵活性的场景下,反射是一个不可或缺的工具。理解和掌握反射机制,可以帮助我们编写出更加灵活和强大的Java程序。