当前位置: 首页 > news >正文

Java 反射机制深度解析:类信息的来源、declared 的区别、赋值操作及暴力反射

在 Java 开发中,反射机制是一个强大且灵活的工具,它允许程序在运行时动态地获取类的信息、创建对象、调用方法和访问字段等。本文将结合代码示例和图示,深入探讨以下四个问题:

  1. 类信息来自哪里?

  2. 获取类信息时加不加 declared 的区别是什么?

  3. 如何使用类信息进行赋值操作?

  4. 什么是暴力反射?如何使用暴力反射?

一、类信息来自哪里?

类信息的来源可以分为以下几个阶段:

  1. 源码阶段:开发者编写的 .java 文件,定义了类的结构,包括字段、方法和构造器等。

  2. 编译阶段:通过 javac 编译器将 .java 文件编译为 .class 文件,类的信息被存储在字节码文件中。

  3. 加载阶段:JVM 的类加载器(ClassLoader)将 .class 文件加载到内存中,并生成对应的 Class 对象。

  4. 运行阶段:通过反射机制,程序可以动态获取 Class 对象中的字段、方法和构造器等信息。

1. 获取 Class 对象的三种方式

在 Java 中,可以通过以下三种方式获取 Class 对象:

  1. 通过全类名获取

    Class clazz1 = Class.forName("com.gcby.Student");
  2. 通过类的 .class 属性获取

    Class clazz2 = Student.class;
  3. 通过实例的 .getClass() 方法获取

    Student student = new Student();
    Class clazz3 = student.getClass();

2. 三种方式的等价性

通过上述三种方式获取的 Class 对象是同一个对象,可以使用 == 进行比较:

System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true

3. 类信息的加载过程

类信息的加载过程如下图所示:

  1. 源码文件xxx.java

  2. 编译生成字节码文件xxx.class

  3. 类加载器加载字节码文件:生成 Class 对象,包含字段、方法和构造器等信息。

  4. 运行时访问类信息:通过反射机制动态获取类信息。

二、Student 类的定义

为了更好地理解反射机制,我们先来看一下 Student 类的定义:

package com.gcby;

public class Student {
    private String name; // 私有字段
    public int age;       // 公共字段
    double height;        // 默认访问权限字段
    protected char sex;   // 受保护字段

    // 私有方法
    private void run() {
        System.out.println("Running...");
    }

    // 公共方法
    public int getAge() {
        return this.age;
    }

    // 默认访问权限方法
    String getNameString(String name) {
        return name;
    }

    // 受保护方法
    protected void run(String a, int b) {
        System.out.println(a + " " + b);
    }

    // 构造器
    public Student(String name, int age, double height, char sex) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.sex = sex;
    }

    public Student(double height, char sex) {
        this.height = height;
        this.sex = sex;
    }
}

三、获取类信息时加不加 declared 的区别

在使用反射机制获取类信息时,getDeclaredFields()getDeclaredMethods()getDeclaredConstructors() 方法与 getFields()getMethods()getConstructors() 方法有显著区别:

  1. getDeclared 方法

    • 获取当前类中声明的所有字段、方法或构造器,包括私有(private)、受保护(protected)、默认(包访问权限)和公共(public)的成员。

    • 不会获取父类的成员。

  2. get 方法

    • 获取当前类及其父类中所有公共(public)的字段、方法或构造器。

    • 不会获取非公共成员。

1. 示例代码

获取字段
Field[] fields = clazz.getDeclaredFields(); // 获取所有声明的字段
Field[] publicFields = clazz.getFields();   // 获取所有公共字段
获取方法
Method[] methods = clazz.getDeclaredMethods(); // 获取所有声明的方法
Method[] publicMethods = clazz.getMethods();   // 获取所有公共方法
获取构造器
Constructor[] constructors = clazz.getDeclaredConstructors(); // 获取所有声明的构造器
Constructor[] publicConstructors = clazz.getConstructors();   // 获取所有公共构造器

2. 示例输出

假设 Student 类如下:

public class Student {
    private String name;
    public int age;
    double height;
    protected char sex;

    private void run() {}
    public int getAge() { return age; }
}
使用 getDeclaredFields()
Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
// 输出:
// [field Student.name, field Student.age, field Student.height, field Student.sex]
使用 getFields()
Field[] publicFields = clazz.getFields();
System.out.println(Arrays.toString(publicFields));
// 输出:
// [field Student.age]
使用 getDeclaredMethods()
Method[] methods = clazz.getDeclaredMethods();
System.out.println(Arrays.toString(methods));
// 输出:
// [method void Student.run(), method int Student.getAge()]
使用 getMethods()
Method[] publicMethods = clazz.getMethods();
System.out.println(Arrays.toString(publicMethods));
// 输出:
// [method int Student.getAge()]

四、如何使用类信息进行赋值

通过反射机制,可以动态地为对象的字段赋值,即使字段是私有的。以下是具体步骤:

  1. 获取类对象

    Class clazz = Class.forName("com.gcby.Student");
  2. 创建实例

    Constructor constructor = clazz.getConstructor();
    Student student = (Student) constructor.newInstance();
  3. 获取字段并赋值

    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true); // 忽略访问权限修饰符
    nameField.set(student, "张三");
  4. 调用方法

    Method runMethod = clazz.getDeclaredMethod("run");
    runMethod.setAccessible(true);
    runMethod.invoke(student);

1. 示例代码

完整代码如下:

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

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("com.gcby.Student");

        // 创建实例
        Constructor constructor = clazz.getConstructor();
        Student student = (Student) constructor.newInstance();

        // 获取字段并赋值
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(student, "张三");

        Field ageField = clazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.set(student, 18);

        Field heightField = clazz.getDeclaredField("height");
        heightField.setAccessible(true);
        heightField.set(student, 185.5);

        Field sexField = clazz.getDeclaredField("sex");
        sexField.setAccessible(true);
        sexField.set(student, '男');

        // 调用方法
        Method runMethod = clazz.getDeclaredMethod("run");
        runMethod.setAccessible(true);
        runMethod.invoke(student);

        Method getAgeMethod = clazz.getDeclaredMethod("getAge");
        System.out.println("Age: " + getAgeMethod.invoke(student)); // 输出:Age: 18
    }
}

2. 输出结果

Running...
Age: 18

五、什么是暴力反射?

暴力反射(也称为“强制反射”)是指通过反射机制绕过 Java 的访问控制限制,访问或修改类的私有成员。在 Java 中,私有成员(如私有字段和私有方法)在默认情况下是不可访问的,但通过反射机制可以强制访问这些成员。

1. 暴力反射的原理

暴力反射的核心在于 setAccessible(true) 方法。该方法可以绕过 Java 的访问控制限制,允许访问或修改私有成员。以下是具体步骤:

  1. 获取类对象

    Class clazz = Class.forName("com.gcby.Student");
  2. 获取私有字段或方法

    Field privateField = clazz.getDeclaredField("name");
    Method privateMethod = clazz.getDeclaredMethod("run");
  3. 设置访问权限

    privateField.setAccessible(true);
    privateMethod.setAccessible(true);
  4. 访问或修改私有成员

    privateField.set(student, "张三");
    privateMethod.invoke(student);

2. 示例代码

完整代码如下:

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

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("com.gcby.Student");

        // 创建实例
        Student student = (Student) clazz.newInstance();

        // 获取私有字段并赋值
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(student, "张三");

        // 获取私有方法并调用
        Method runMethod = clazz.getDeclaredMethod("run");
        runMethod.setAccessible(true);
        runMethod.invoke(student);

        // 输出结果
        System.out.println("Name: " + nameField.get(student)); // 输出:Name: 张三
    }
}

3. 输出结果

Running...
Name: 张三

六、总结

  1. 类信息的来源:类信息从源码文件经过编译生成字节码文件,再由类加载器加载到内存中,生成 Class 对象。

  2. getDeclaredget 的区别getDeclared 获取当前类中声明的所有成员,包括私有成员;get 只获取公共成员,包括父类的公共成员。

  3. 使用类信息赋值:通过反射机制,可以动态获取字段并赋值,即使字段是私有的,也可以通过 setAccessible(true) 忽略访问权限修饰符。

  4. 暴力反射:暴力反射通过 setAccessible(true) 方法绕过 Java 的访问控制限制,允许访问或修改私有成员。虽然暴力反射在某些场景下非常有用,但在实际开发中应谨慎使用,避免破坏类的封装性和安全性。

反射机制是 Java 中的强大工具,但在实际开发中应谨慎使用,避免破坏封装性导致代码难以维护。

相关文章:

  • 如何避免redis长期运行持久化AOF文件过大的问题:AOF重写
  • Docker安装Quickwit搜索引擎
  • 捷米特 JM - RTU - TCP 网关应用 F - net 协议转 Modbus TCP 实现电脑控制流量计
  • 【gRPC】:快速上手gRPC与protobuf
  • 深入理解 C++17 中的 std::launder
  • 常用标准库之-std::iota
  • 初等数论--欧几里得算法
  • 网络技术变迁:从IPv4走向IPv6
  • Java基础(其一)
  • 计算机视觉:神经网络实战之手势识别
  • 【Python pro】基本数据类型
  • 算法——舞蹈链算法
  • 个人博客5年回顾
  • 巴克传动(航天伺服生产)MES系统规划方案
  • java 方法引用
  • 【Unity动画】导入动画资源到项目中,Animator播放角色动画片段,角色会跟随着动画播放移动。
  • C++ 如何销毁进程
  • 在Windows和Linux平台上使用c++获取文件当前路径
  • [AI相关]Unity的C#代码如何简写
  • 探索JavaScript网页设计的无限可能:从基础到AI集成
  • 宇树科技王兴兴:第一桶金来自上海,欢迎上海的年轻人加入
  • 招商蛇口:今年前4个月销售额约498.34亿元
  • 烈士沈绍藩遗孤、革命家帅孟奇养女舒炜逝世,享年96岁
  • 中日有关部门就日本水产品输华问进行第三次谈判,外交部回应
  • 国家税务总局泰安市税务局:山东泰山啤酒公司欠税超536万元
  • 重庆党政代表团在沪考察,陈吉宁龚正与袁家军胡衡华共商两地深化合作工作