• 作者:老汪软件技巧
  • 发表时间:2024-11-07 07:01
  • 浏览量:

什么是反射

反射是 Java 中的一个特性,它允许程序在运行时获取自身的信息,并动态地操作类或对象的属性、方法和构造函数。通过反射,我们可以在事先不知道确切类名的情况下实例化对象、调用方法和设置属性。

反射机制的核心是Class对象,它代表一个类。Java 虚拟机(JVM)在加载类时会自动创建这个Class对象。

JVM 如何创建一个类

当我们编写一个类并进行编译时,编译器会将其转换为存储在.class文件中的字节码。在类加载过程中,JVM 使用ClassLoader读取.class文件,将字节码加载到内存中,并根据这些信息创建相应的Class对象。由于每个类在 JVM 中只加载一次,所以每个类都对应一个唯一的Class对象。

示例

public class User extends People {
    public String name;
    private int age;
    private static int staticFiled = 10;
    private final String sex;
    protected String protectedFiled;
    static {
        System.out.println("静态方法执行");
    }
    public User(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
    private void privateMethod() {
        System.out.println("我是私有方法");
    }
    public void publicMethod() {
        System.out.println("我是公共方法");
    }
}
public class People {
    public String publicFiled;
    private String privateFiled;
}

获取Class对象的三种方式第一种方法通过类名使用.class获取类对象。这是在编译时完成的,所以明确指定了类型User,不会导致任何错误。使用这种方法获取对象不会触发类初始化;只有在访问类的静态成员或实例时才会进行初始化。

Class userClass = User.class;

实例化一个对象:

User userInstance = userClass.getDeclaredConstructor(String.classString.class).newInstance("张三""男");

第二种方法通过对象的getClass()方法获取类对象。这种方法适用于从已实例化的类对象中获取类对象。请注意,类型不是User,而是通配符?,因为Class对象是从User的实例中获取的,实例的具体类型只能在运行时确定,而不是在编译时。

User user = new User("张三""男");
Class userClass = user.getClass();

实例化一个对象:

Constructor constructor = userClass.getConstructor(String.class, String.class);
User userInstance = (User) constructor.newInstance("张三""男");

第三种方法使用静态方法Class.forName()通过全路径获取类对象。由于类型只能在运行时知道,所以类型是通配符?。通过这种方法获取类对象将立即触发类初始化。

Class userClass = Class.forName("org.example.reflect.entity.User");

创建一个实例:

Constructor constructor = userClass.getDeclaredConstructor(String.class, String.class);
User userInstance = (User) constructor.newInstance("张三""男");

在 Java 中访问对象字段获取所有公共字段要获取所有公共字段,包括从父类继承的字段,使用getFields():

Field[] fields = user.getFields();
for (Field field : fields) {
    System.out.println(field);
}

输出:

public java.lang.String org.example.reflect.entity.User.name
public java.lang.String org.example.reflect.entity.People.publicField

获取所有声明的字段要获取类中所有声明的字段,无论其访问级别如何,使用getDeclaredFields()。这不包括从超类继承的字段:

_反射透射散射_反射private

Field[] fields = user.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}

输出:

public java.lang.String org.example.reflect.entity.User.name
private int org.example.reflect.entity.User.age
private final java.lang.String org.example.reflect.entity.User.sex
protected java.lang.String org.example.reflect.entity.User.protectedField

获取超类中的字段要获取超类中的字段,使用getSuperclass():

Field[] fields = user.getSuperclass().getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}

输出:

public java.lang.String org.example.reflect.entity.People.publicField
private java.lang.String org.example.reflect.entity.People.privateField

获取特定字段要通过名称获取特定公共字段,使用getField(String name)。对于任何特定字段,无论其访问级别如何,使用getDeclaredField(String name)。

处理不存在的字段尝试访问不存在的字段不会产生编译时错误,但会在运行时抛出异常:

try {
    Field nonExistentField = user.getDeclaredField("nonExistentField");
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

输出:

java.lang.NoSuchFieldException: nonExistentField

设置字段值要设置私有静态字段的值,首先使其可访问:

Class userClass = Class.forName("org.example.reflect.entity.User");
Field staticField = userClass.getDeclaredField("staticField");
staticField.setAccessible(true);
System.out.println(staticField.get(null));

如果字段是final的,仍然可以修改它:

Field field = userClass.getDeclaredField("sex");
field.setAccessible(true);
field.set(obj, "女生");
System.out.println(field.get(obj));

输出:

女生

访问方法

访问方法与访问字段类似:

总结

从上面的示例中可以看出,以Declared为前缀的方法(如getDeclaredField)用于检索所有字段或方法,无论其访问级别如何。相比之下,没有Declared的方法(如getField)仅检索公共字段或方法。

反射允许绕过访问控制检查。用private或final修饰的字段和方法可以被访问和修改,这破坏了封装性。因此,应该谨慎使用。