一文讲透 Java 中的反射

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

什么是反射

反射是 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对象的三种方式

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

Class<User> userClass = User.class;

实例化一个对象:

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

(2) 第二种方法通过对象的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("张三", "男");

(3) 第三种方法使用静态方法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

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

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

(3) 获取超类中的字段要获取超类中的字段,使用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

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

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

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

输出:

java.lang.NoSuchFieldException: nonExistentField

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

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));

输出:

女生

访问方法

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

  • getMethods()检索类及其超类中的所有公共方法。
  • getDeclaredMethods()检索类中所有声明的方法,无论访问级别如何。
  • getMethod(String name, Class<?>... parameterTypes)按名称和参数类型检索特定公共方法。
  • getDeclaredMethod(String name, Class<?>... parameterTypes)按名称和参数类型检索特定声明的方法,无论访问级别如何。

总结

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

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

责任编辑:赵宁宁 来源: 程序猿技术充电站
相关推荐

2020-03-26 09:18:54

高薪本质因素

2024-08-13 17:09:00

架构分库分表开发

2020-08-04 10:56:09

进程线程协程

2020-07-16 09:02:45

aPaaS云计算aPaaS平台

2023-11-09 08:41:25

DevOpsAIOps软件

2021-01-18 13:05:52

Serverless Serverfull FaaS

2020-12-01 11:34:14

Elasticsear

2024-08-07 10:54:59

正则表达式Java RegexJava

2023-05-04 08:24:52

ChatGPT产品经理工业革命

2024-05-31 13:23:19

OceanBase单机版架构

2020-05-20 09:55:42

Git底层数据

2024-07-10 12:00:42

2021-06-29 12:10:00

CRC校验码C语言

2020-01-02 09:06:23

微服务数据框架

2020-07-16 07:30:15

数据库SQL技术

2022-05-15 09:16:28

IPv6IPIP地址

2023-05-11 08:16:13

可视化监控工具Kafka

2024-11-07 22:57:30

2022-04-28 10:41:08

SaaS业务方式

2022-03-10 07:58:12

ReactorNetty运转架构
点赞
收藏

51CTO技术栈公众号