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 中的强大工具,但在实际开发中应谨慎使用,避免破坏封装性导致代码难以维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/20226.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Transformer 模型介绍(四)——编码器 Encoder 和解码器 Decoder

上篇中讲完了自注意力机制 Self-Attention 和多头注意力机制 Multi-Head Attention,这是 Transformer 核心组成部分之一,在此基础上,进一步展开讲一下编码器-解码器结构(Encoder-Decoder Architecture) Transformer 模…

电脑系统损坏,备份文件

一、工具准备 1.U盘:8G以上就够用,注意会格式化U盘,提前备份U盘内容 2.电脑:下载Windows系统并进行启动盘制作 二、Windows启动盘制作 1.微软官网下载启动盘制作工具微软官网下载启动盘制作工具https://www.microsoft.com/zh-c…

Linux下Ollama下载安装速度过慢的解决方法

问题描述:在Linux下使用默认安装指令安装Ollama,下载安装速度过慢,进度条进度缓慢,一直处于Downloading Linux amd64 bundle中,具体如下图所示: 其中,默认的Ollama Linux端安装指令如下&#xf…

uniapp中@input输入事件在修改值只有第一次有效的问题解决

在uniapp中使用输入框,要求输入不超过7个字,所以需要监听输入事件,当每次输入文字的时候,就把输入的值截断,取前7个值。但是在input事件中,重新赋值的值发生了变化,但是页面上的还是没有变&…

DeepSeek 助力 Vue 开发:打造丝滑的范围选择器(Range Picker)

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…

VMware按照的MacOS升级后无法联网

背景 3年前公司使用Flutter开发了一款app,现在app有微小改动需要重新发布到AppStore 问题 问题是原来的Vmware搭建的开发环境发布App失败了 提示:App需要使用xcode15IOS 17 SDK重新构建,这样的话MacOS至少需要升级到13.5 Xcode - 支持 - Ap…

Day01 【苍穹外卖】环境搭建与前后端联调

一、环境搭建 1.JDK安装与IDEA安装: JDK安装与IDEA安装:【JAVA基础】01、JAVA环境配置----JDK与 IDEA集成开发环境的安装(2025最新版本)_配置jdk-CSDN博客 注意,这里要下载JDK1.8版本的,不然会报错&…

STM32 HAL库USART串口中断编程:环形缓冲区防止数据丢失

HAL_UART_Receive接收最容易丢数据了,可以考虑用中断来实现,但是HAL_UART_Receive_IT还不能直接用,容易数据丢失,实际工作中不会这样用,STM32 HAL库USART串口中断编程:演示数据丢失,需要在此基础优化一下. 本文介绍STM32F103 HAL库USART串口中断,利用环形缓冲区来防…

Vulnhub:DC-1靶机渗透

渗透过程 一,信息收集 1,探测目标IP地址 探测目标IP地址,探测主机的工具有很多,常见的有arp-scan、nmap还有netdiscover,这里使用arp-scan arp-scan -l确定了DC-1主机的IP地址为 192.168.126.1452,探测…

MySQL 之存储引擎(MySQL Storage Engine)

MySQL 之存储引擎 常见存储引擎及其特点 ‌InnoDB‌: ‌特点‌:支持事务处理、行级锁定、外键约束,使用聚簇索引,适合高并发读写和事务处理的场景‌。‌适用场景‌:需要高可靠性、高并发读写和事务处理的场景‌。 ‌M…

EasyX安装及使用

安装链接:EasyX Graphics Library for C 安装完成包含头文件graphics.h即可使用 RGB合成颜色(红色部分,绿色部分,蓝色部分) 每种颜色的值都是(0~255) 坐标默认的原点在窗口的左上角&#xf…

20.【线性代数】——坐标系中,平行四边形面积=矩阵的行列式

三 坐标系中,平行四边形面积矩阵的行列式 定理验证 定理 在坐标系中,由向量(a,b)和向量(c,d)组成平行四边形的面积 矩阵 [ a b c d ] \begin{bmatrix} a&b\\ c&d \end{bmatrix} [ac​bd​]的行列式,即&#x…

Grafana——Rocky9安装Grafana相关步骤记录

安装Grafana 安装 直接进下面这个页面,可以看到这边可以选择版本以及操作系统 并且如果是Linux平台的,下面会给出不同平台的命令,直接复制粘贴执行一下就可以了! 验证 运行命令 ## 运行service systemctl start grafana-server## 自启…

Mathtype安装入门指南

Mathtype安装入门指南 1 mathtype安装及补丁2 mathtype在word中加载3 常见的mathtype快捷命令4 实列测试 1 mathtype安装及补丁 下载相应的Mathtype7.4软件安装包,百度网盘链接为: 百度网盘链接下载完成后,有三个软件,如下图所示…

ConcurrentHashMap 在Jdk 17 不同版本中的优化和改进

ConcurrentHashMap 是 Java 中的一个高性能线程安全的哈希表实现,随着 JDK 版本的迭代,其内部实现也经历了多次优化和改进。每个版本的改动针对不同的场景和需求进行了性能提升和问题修复。以下分别描述了 JDK 7、JDK 8 和 JDK 17 的主要设计和区别&…

普通报表入门

1. 概述 报表设计主要可以分为新建报表、数据准备、报表主体设计、报表预览几大部分。其中报表主体可以分为大标题、小标题、表格数据、结尾几大部分,本文主要以普通报表为例,讲述如何按照报表设计流程快速设计一张报表。FineReport 版本为11.0 1.1 预期…

用deepseek学大模型08-cnn残差网络

残差网络 参考:https://blog.csdn.net/2301_80750681/article/details/142882802 以下是使用PyTorch实现的三层残差网络示例,包含三个残差块和完整的网络结构: import torch import torch.nn as nnclass BasicBlock(nn.Module):expansion…

AIGC(生成式AI)试用 21 -- Python调用deepseek API

1. 安装openai pip3 install openai########################## Collecting openaiUsing cached openai-1.61.1-py3-none-any.whl.metadata (27 kB) Collecting anyio<5,>3.5.0 (from openai)Using cached anyio-4.8.0-py3-none-any.whl.metadata (4.6 kB) Collecting d…

分享一款AI绘画图片展示和分享的小程序

&#x1f3a8;奇绘图册 【开源】一款帮AI绘画爱好者维护绘图作品的小程序 查看Demo 反馈 github 文章目录 前言一、奇绘图册是什么&#xff1f;二、项目全景三、预览体验3.1 截图示例3.2 在线体验 四、功能介绍4.1 小程序4.2 服务端 五、安装部署5.1 快速开始~~5.2 手动部…

node.js + html调用ChatGPTApi实现Ai网站demo(带源码)

文章目录 前言一、demo演示二、node.js 使用步骤1.引入库2.引入包 前端HTML调用接口和UI所有文件总结 前言 关注博主&#xff0c;学习每天一个小demo 今天是Ai对话网站 又到了每天一个小demo的时候咯&#xff0c;前面我写了多人实时对话demo、和视频转换demo&#xff0c;今天…