Java 反射
一. 介绍
Java反射是指在运行时动态地调用、检查或修改类的方法、属性、构造函数等信息的机制。使用反射,可以在程序执行期间通过类的名称获取类的相关信息,并且可以动态地创建对象、调用方法、访问和修改字段的值等。通过反射,我们可以绕过编译时的类型检查,对运行时的类进行操作。
在Java中,反射API主要位于java.lang.reflect包下,提供了一系列类来实现反射功能。
主要提供了以下几个核心类:
Class类:表示一个类或接口,在运行时可以获取类的构造函数、方法、字段等信息。
Constructor类:表示类的构造函数,可以用来创建新的对象实例。
Method类:表示方法,可以用来调用类的方法。
Field类:表示类的字段,可以用来访问和修改类的成员变量。
二. 反射的优势和劣势
优势:
- 动态性:反射允许程序在运行时动态地获取和使用类的信息,而不需要在编译时确定。这使得程序可以根据条件或配置灵活地创建对象、调用方法等。
- 扩展性:通过反射,程序可以与之前未预料到的类进行交互。这使得代码可以更容易地适应变化和扩展。
- 逆向工程:反射可以使程序能够检查和操作其它代码的结构和行为。这对于框架、库和工具的设计和实现非常有用。
劣势:
- 性能开销:由于反射需要在运行时进行额外的检查和处理,因此比直接调用方法或访问字段的性能要低。如果在性能敏感的应用中频繁使用反射,可能会对性能产生负面影响。
- 安全限制:反射可以绕过Java语言的访问控制机制,允许访问和修改本来不应该被访问或修改的成员。这可能导致安全风险,特别是在使用反射时没有适当的权限检查和控制的情况下。
- 复杂性和易错性:反射提供了一种强大而复杂的机制,需要谨慎和深入的理解。不正确的使用反射可能导致运行时异常或不可预料的行为。
三. 代码展示
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException,NoSuchFieldException , InvocationTargetException { // 通过User.class() 获取类对象信息 Class<?> userClass = User.class; // 可以获取构造函数 会报异常: NoSuchMethodException 找不到具有指定名称的字段。 Constructor<?> constructor = userClass.getConstructor(); System.out.println("类:" + constructor); // 使用userClass类对象,获取超类对象信息, Class<?> superclass = userClass.getSuperclass(); System.out.println("User的超类是:" + superclass.getName()); // 使用userClass类对象,获取实例对象信息 会报异常: // IllegalAccessException 安全权限异常 是由于java在反射时调用了private方法所导致的。 // InstantiationException 实例化异常 当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
// 也可以使用构造函数,获取实例对象信息,此处不展示 User user = (User) userClass.newInstance(); // 获取实例对象所有属性名,返回数组[]
// Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。 List<String> fieldNames = new ArrayList<>(); // 建立容器,用于储存属性名 Field[] declaredFields = userClass.getDeclaredFields(); // 获取所有属性名,Field类型 for (Field declaredField : declaredFields) { // 遍历Field类型 获取到每个的类的名字 fieldNames.add(declaredField.getName()); } System.out.println("User类中的属性有" + fieldNames); // NoSuchFieldException ①没有对应字段;②属性为私有时获取Field用的方法不是getDeclaredField。 Field fieldName = userClass.getDeclaredField("name"); fieldName.setAccessible(true); // 设置私有可访问 fieldName.set(user, "小美"); Field fieldAge = userClass.getDeclaredField("age"); fieldAge.setAccessible(true); fieldAge.set(user, 19); Field fieldPhone = userClass.getDeclaredField("phone"); fieldPhone.set(user, "110"); // 获取User类中的方法 Method method = userClass.getMethod("show"); //getMethod() 获取公共方法 // InvocationTargetException 当被调用的方法或构造器内部抛出异常,该异常将会被包装成InvocationTargetException来进行接收 method.invoke(user); // 调用方法 invoke() } 输出结果: 类:public xinhua.User() User的超类是:java.lang.Object User类中的属性有[name, age, phone] 姓名:小美,年龄:19,手机号:110
步骤:
1. 获取Class对象:使用类名.class获取,或使用Class.forName()方法根据类的完全限定名获取Class对象。
2. 创建实例对象:使用Class对象的newInstance()方法或Constructor(构造方法)对象的newInstance()方法。
3. 获取字段信息:通过Class对象的getField()或getDeclaredField()方法获取字段的Field对象。前者只能获取公共字段,后者可以获取所有字段。
4. 访问/修改字段值:通过Field对象的get()和set()方法分别获取和设置字段的值。在访问私有字段时,需要先调用setAccessible(true)开启访问权限。
5. 获取方法信息:通过Class对象的getMethod()或getDeclaredMethod()方法获取方法的Method对象。前者只能获取公共方法,后者可以获取所有方法。
6. 调用方法:通过Method对象的invoke()方法调用方法。如果方法具有参数,需要提供相应的参数列表。
四. 常用方法
1. 获取Class对象的三种方式:
(1)使用 Class.forName("类名") 方法可以根据类的全限定名来获取对应的Class对象。
(2)使用 对象.getClass() 方法可以获取对象所属的Class对象。
(3)使用 类名.Class 后缀可以直接获取某个类的Class对象。
// Class.forName("类名") ClassNotFoundException 异常 JVM无法找到classpath请求的类发生 Class<?> userClass = Class.forName("xinhua.User"); // 对象.getClass() 方法 因为是使用对象.class获取到的Class类对象信息,所以不会报 ClassNotFoundException 异常 User user = new User(); Class<? extends User> userClass = user.getClass(); // 类名.Class 直接使用User类,所以没有报异常。 Class<User> userClass = User.class;
2. 获取构造方法的两种方式:
(1)通过Class对象的getConstructor()方法获取指定参数类型的公共构造函数。例如,使用getConstructor(Class<?>... parameterTypes)方法可以获取指定参数类型的公共构造函数。如果构造函数是私有的,可以使用getDeclaredConstructor()方法。
// 使用 getConstructor(Class<?>... parameterTypes) 抛出:NoSuchMethodException 找不到具有指定名称的字段。 Constructor<?> constructor = userClass.getConstructor(); //默认无参构造方法 Constructor<?> constructor = userClass.getConstructor(String.class, int.class); // 带参构造方法,输入参数类型的class类 // 使用 getDeclaredConstructor(Class<?>... parameterTypes) 抛出:NoSuchMethodException 找不到具有指定名称的字段。 Constructor<?> constructor = userClass.getDeclaredConstructor(); Constructor<?> parameterConstructor = userClass.getDeclaredConstructor(String.class);
(2)通过Class对象的getDeclaredConstructors()方法获取所有的构造函数,无论是否为公共构造函数。这个方法返回一个Constructor数组,其中包含了类中声明的所有构造函数。通过遍历这个数组,可以获取所有构造函数的信息。
Constructor<?>[] constructors = userClass.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { System.out.println(constructor); } 输出结果: public xinhua.User(java.lang.String,java.lang.Integer,java.lang.String) public xinhua.User(java.lang.String,java.lang.Integer) public xinhua.User()
3. 创建对象实例的两种方式:
实例化对象 newInstance() 的时候会抛出异常:IllegalAccessException、InstantiationException
IllegalAccessException 非法访问异常 ①方法或字段私有化 ②不兼容的数据类型 ③未正确设置反射权限
InstantiationException 实例化异常 该类没有默认构造函数或默认构造函数无法实例化,就会抛出这个异常。
(1)通过Class类的对象获取实例对象:可以使用Class类的newInstance()方法,此种是调用默认的无参构造方法来实例化对象(jdk 9 已弃用)。
User user = (User) userClass.newInstance();
(2)通过构造方法获取实例对象:可以使用getConstructor()或getDeclaredConstructor()方法获取指定构造方法,然后使用newInstance()方法来创建对象。
调用构造方法 抛出异常:NoSuchMethodException ①所调用的方法名称错误 ②所调用的方法的参数类型与方法定义的参数类型不匹配 ③所调用的方法的访问修饰符不正确 ④所调用的方法位于不正确的类或接口中
// 使用构造函数 创建实例对象 Constructor<?> constructor = userClass.getConstructor(); User user = (User) constructor.newInstance(); // 带参构造方法 Constructor<?> constructor = userClass.getConstructor(String.class, Integer.class); User user = (User) constructor.newInstance("小美",18); // 获取所有构造方法 Constructor<?>[] constructors = userClass.getConstructors(); for (Constructor<?> constructor : constructors) { // 根据参数类型判断是否为需要的构造方法 // 获取构造方法的参数个数 int parameterCount = constructor.getParameterCount(); // 获取构造方法的参数类型 Class<?>[] parameterTypes = constructor.getParameterTypes();
// 判断所需要使用的构造方法,需要2个参数,第一个为String类型,第二个为Integer类型 if (parameterCount == 2 && parameterTypes[0] == String.class && parameterTypes[1] == Integer.class) { User user = (User) constructor.newInstance("小美", 18); } }
4. 获取类的方法的三种方式:
(1)使用Class对象的getMethod()方法:通过传入方法名和参数类型来获取指定的公共方法。
// 获取类的方法 Method method = userClass.getMethod("show"); // 无参方法 Method method = userClass.getMethod("show", String.class, Integer.class); //指定参数类型的方法
(2)使用Class对象的getDeclaredMethod()方法:通过传入方法名和参数类型来获取指定的任意方法,即使它是私有的。这种方式可以获取到类的所有方法。
// 可获取私有类的方法 Method method = userClass.getDeclaredMethod("show"); Method method = userClass.getDeclaredMethod("show", String.class, Integer.class);
(3)使用Class对象的getMethods()方法或getDeclaredMethods()方法:分别获取类的所有公共方法或所有方法,返回一个Method数组,然后可以遍历该数组来获取具体的方法。
// 获取所有类方法 Method[] methods = userClass.getMethods(); Method[] declaredMethods = userClass.getDeclaredMethods();
5. 获取方法名、返回值、参数;
(1) 获取方法名称 :使用方法.getName();
(2) 获取方法返回值:使用方法.getReturnType();
(3) 获取方法参数:使用方法.getParameterTypes();
注意:私有的方法需要使用Method对象的setAccessible()方法设置为true,以允许访问私有的方法
method.setAccessible(true); // 允许访问私有的方法 String methodName = method.getName(); // 获取方法名称 Class<?> methodReturnType = method.getReturnType(); // 获取方法返回值 Class<?>[] parameterTypes = method.getParameterTypes(); // 获取方法参数
6. 方法的调用:
(1)method.invoke(null, Object... args)方法可以用来调用指定的方法。
第一个参数是要调用的方法所属的类或对象(如果是静态方法,则为null),第二个参数列表,是Object类型的可变参数列表,是要传递给方法的参数。
注意,要确保传递参数的数量和类型与方法定义中的参数匹配,否则可能会出现异常。
method.invoke(user, "小美"); //非静态方法 method.invoke(null, "小美"); //调用静态方法
7. 获取成员变量的三种方法:
(1)使用Class对象的getField()方法:该方法只能获取到public修饰的成员变量,包括从父类继承而来的公共成员变量。
// 获取公共成员变量 Field phone = userClass.getField("phone"); // phone 为user中的公共字段 String strPhone = (String) phone.get(user);
(2)使用Class对象的getDeclaredField()方法:该方法可以获取到所有类型的成员变量,无论其访问修饰符是public、protected、private还是默认访问修饰符。
在获取私有成员变量时,需要先调用setAccessible(true)方法将其可访问性设置为true,才能进行访问。
// 获取私有成员变量 Field name = userClass.getDeclaredField("name"); name.setAccessible(true); // 允许私有可访问 String strName = (String) name.get(user);
(3)使用getFields()方法和getDeclaredFields()方法:这两个方法分别返回一个类的所有public成员变量和该类所有声明的成员变量。这些方法都返回一个Field对象数组,通过遍历该数组可以获取所有成员变量。
getModifiers() 返回一个整数值,用于获取某个类、字段或方法的修饰符
Modifier.isPrivate() 是 Java 中的一个静态方法,用于判断指定的修饰符是否为 private。
Modifier.isPublic() 是 Java 中的一个静态方法,用于判断指定的修饰符是否为 public。
// 获取公共成员变量 Field[] fields = userClass.getFields(); for (Field field : fields) { if (Modifier.isPublic(field.getModifiers())) { Object o = field.get(user); } } // 获取所有成员变量 Field[] declaredFields = userClass.getDeclaredFields(); for (Field declaredField : declaredFields) { if (Modifier.isPrivate(declaredField.getModifiers())) { declaredField.setAccessible(true); // 如果是私有化,则开启允许访问 } Object o = declaredField.get(user); }
8. 访问成员变量:
(1)使用 get(Object obj) 方法可以获取指定对象上该成员变量的值,Object一般使用实例对象。
代码如上,获取成员变量中已展示 // 获取其中某个成员变量,需要根据参数类型做出强转。 String name = (String) declaredField.get(user);
9. 修改成员变量:
(1)使用 set(Object obj, Object value) 方法可以设置指定对象上该成员变量的值,第一个参数是要设置值的对象实例,第二个参数是要设置的值。
Field name = userClass.getDeclaredField("name"); name.set(user, "小明"); Field age = userClass.getDeclaredField("age"); age.set(user, 20);
四. 扩展
Spring框架中,反射被广泛应用于以下方面:
-
依赖注入(Dependency Injection):Spring使用反射来解析和创建bean对象,通过读取配置信息或使用注解来确定类的属性和依赖关系,并使用反射机制动态地实例化和注入这些依赖项。
-
AOP(Aspect-Oriented Programming):Spring的AOP功能使用了反射来实现动态代理。它通过在运行时创建代理对象并在目标方法执行前后进行处理,从而实现切面逻辑的织入。
-
动态代理(Dynamic Proxies):Spring使用反射来生成动态代理,以实现声明式事务管理、远程调用等功能。通过将方法调用转发给代理处理,Spring能够在目标方法执行前后进行拦截和增强。