Java 反射

一. 介绍

  Java反射是指在运行时动态地调用、检查或修改类的方法、属性、构造函数等信息的机制。使用反射,可以在程序执行期间通过类的名称获取类的相关信息,并且可以动态地创建对象、调用方法、访问和修改字段的值等。通过反射,我们可以绕过编译时的类型检查,对运行时的类进行操作

  

  在Java中,反射API主要位于java.lang.reflect包下,提供了一系列类来实现反射功能。

  主要提供了以下几个核心类:

    Class类:表示一个类或接口,在运行时可以获取类的构造函数、方法、字段等信息。

    Constructor类:表示类的构造函数,可以用来创建新的对象实例。

    Method类:表示方法,可以用来调用类的方法。

    Field类:表示类的字段,可以用来访问和修改类的成员变量。

 

二. 反射的优势和劣势

  优势:

  1. 动态性:反射允许程序在运行时动态地获取和使用类的信息,而不需要在编译时确定。这使得程序可以根据条件或配置灵活地创建对象、调用方法等。
  2. 扩展性:通过反射,程序可以与之前未预料到的类进行交互。这使得代码可以更容易地适应变化和扩展。
  3. 逆向工程:反射可以使程序能够检查和操作其它代码的结构和行为。这对于框架、库和工具的设计和实现非常有用。

  劣势:

  1. 性能开销:由于反射需要在运行时进行额外的检查和处理,因此比直接调用方法或访问字段的性能要低。如果在性能敏感的应用中频繁使用反射,可能会对性能产生负面影响。
  2. 安全限制:反射可以绕过Java语言的访问控制机制,允许访问和修改本来不应该被访问或修改的成员。这可能导致安全风险,特别是在使用反射时没有适当的权限检查和控制的情况下。
  3. 复杂性和易错性:反射提供了一种强大而复杂的机制,需要谨慎和深入的理解。不正确的使用反射可能导致运行时异常或不可预料的行为。

 

三. 代码展示

    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框架中,反射被广泛应用于以下方面:

  1. 依赖注入(Dependency Injection):Spring使用反射来解析和创建bean对象,通过读取配置信息或使用注解来确定类的属性和依赖关系,并使用反射机制动态地实例化和注入这些依赖项。

  2. AOP(Aspect-Oriented Programming):Spring的AOP功能使用了反射来实现动态代理。它通过在运行时创建代理对象并在目标方法执行前后进行处理,从而实现切面逻辑的织入。

  3. 动态代理(Dynamic Proxies):Spring使用反射来生成动态代理,以实现声明式事务管理、远程调用等功能。通过将方法调用转发给代理处理,Spring能够在目标方法执行前后进行拦截和增强。

 

 

 

posted @ 2023-07-15 02:26  一只礼貌狗  阅读(135)  评论(0编辑  收藏  举报