七、反射

# 七、反射

# 1、 什么是Java的反射?

JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取类的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

# 2、 获取 Class 对象的方式?

  • 1、知道具体类的情况下可以使用:
类名.class //不会触发类的初始化
  • 2、知道类全限定名称的情况下
Class.forName("类的全限定名"); //会触发类的初始化

  • 3、知道具体对象的情况下:
Object obj = new Object(); // 会触发类的初始化
obj.getClass();
  • 4、通过类加载器获取:可以使用类加载器来获取 Class 对象。这种方式通常用于动态加载类,可以根据类的名称获取 Class 对象。例如:
package com.kinggm.test;

public class TestD {

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = TestD.class.getClassLoader(); // 这里也可以自定义类加载器
        Class<?> clazz = classLoader.loadClass("com.kinggm.test.TestD");  // 不会触发类的初始化
    }

}

需要注意到是: 通过类加载器获取 Class 对象不会触发类的初始化过程,这意味着类加载器加载的类不会执行静态代码块或初始化静态对象。 这种延迟初始化的特性可以在某些场景下提供性能优势,因为只有在需要使用类时才会进行初始化操作。

类初始化的条件:

  • 创建类的实例
  • 访问类或接口的静态字段,最终解析为初始化一个类
  • 调用类的静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用,这要求类进行初始化
  • 初始化一个类的子类,导致其父类被初始化(但接口的初始化不受此规则影响)
  • 虚拟机启动时,需要初始化包含main方法的那个类(主类)

# 3、反射机制优缺点

优点:

  • 运行期类型的判断,动态加载类,提高代码灵活度。

缺点:

  • 1,性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。
  • 2,安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。

# 4、反射的应用场景

  • 我们在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序;
  • Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系;
  • 动态配置实例的属性;
  • JDK提供的动态代理功能;(代理对象通过反射的invoke方法调用被代理对象的原始方法)
  • 注解功能的实现 (基于反射去获取注解标记,然后实现对应的功能);

# 5、反射的用法代码示例

需要注意的小知识点: getDeclaredXXX()getXXX() 方法都用于获取类中的字段、方法、构造方法等信息,但它们之间有一些区别, 下面以getDeclaredField 方法和getField 方法举例:

  • ①、访问权限:
    getDeclaredField(String name) 方法可以获取类中声明的所有字段,包括公有(public)、私有(private)、受保护(protected)和默认(default)访问权限的字段。 但是,如果字段是私有的,就需要通过 setAccessible(true) 方法设置字段的可访问性才能访问。
    getField(String name) 方法只能获取类中的公有字段,无法获取私有、受保护或默认访问权限的字段。

  • ②、查找范围:
    getDeclaredField(String name) 方法在当前类中查找指定名称的字段。
    getField(String name) 方法在当前类及其父类(包括父类的父类等)中查找指定名称的公有字段。

  • ③、用途:
    getDeclaredField() 通常用于获取类中所有类型的字段,无论是公有还是私有的。
    getField() 主要用于获取类的公有字段,适用于对外部公开的字段。


常用的反射API:

  • 获取类的对象:
Class<?> aClass = Class.forName("com.kinggm.test.TestReflect");

  • 获取类的构造器:
Constructor<?> constructor = aClass.getDeclaredConstructor();
  • 获取类的实例:
TestReflect reflect = (TestReflect)constructor.newInstance();  // 或者直接 aClass.newInstance();
  • 获取类的某个字段对象,获取字段值:
Field nameField = aClass.getDeclaredField("name");
nameField.setAccessible(true);  // 如果修饰符是private  需要 setAccessible(true)
// 根据字段对象 获取字段的值
Object value = nameField.get(reflect);
System.out.println(value);
  • 获取类的某个方法对象,执行该方法:
// 获取getName方法对象
Method method = aClass.getDeclaredMethod("getName");
// 执行getName方法
Object invoke = method.invoke(reflect);
System.out.println(invoke);
  • 获取类的全部字段:
// 获取全部字段
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
    System.out.println("字段名:"+declaredField.getName());
}
  • 获取类的全部方法:
// 获取全部方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
    System.out.println("方法名:"+declaredMethod.getName());
}

下面是完整代码:


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TestD {

    public static void main(String[] args) throws Exception{

        Class<?> aClass = Class.forName("com.kinggm.test.TestReflect");
        Constructor<?> constructor = aClass.getDeclaredConstructor();

        // 通过反射 获取类的实例
        TestReflect reflect = (TestReflect)constructor.newInstance();  // 或者直接 aClass.newInstance();
        // 通过对象调用方法
        String name = reflect.getName();
        System.out.println(name);

        // 获取字段对象
        Field nameField = aClass.getDeclaredField("name");
        nameField.setAccessible(true);

        // 根据字段对象 获取字段的值
        Object value = nameField.get(reflect);
        System.out.println(value);

        // 获取getName方法对象
        Method method = aClass.getDeclaredMethod("getName");
        // 执行getName方法
        Object invoke = method.invoke(reflect);
        System.out.println(invoke);

        // 获取全部字段
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("字段名:"+declaredField.getName());
        }

        // 获取全部方法
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("方法名:"+declaredMethod.getName());
        }

    }

}

class TestReflect{
    static {
        System.out.println("执行了类初始化");
    }

    private String name = "秀逗";

    public String getName(){
        return name;
    }
}