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

Java练习——day1(反射)

文章目录

  • 练习1
  • 练习2
  • 练习3
  • 思考
    • 封装原则与反射
    • 合理使用反射“破坏”封装的场景

练习1

编写代码,通过反射获取String类的所有公共方法名称,并按字母顺序打印。

  • 示例代码:
import java.lang.reflect.Method;
import java.util.Arrays;public class StringMethodsReflection {public static void main(String[] args) {// 获取 String 类的所有公共方法(包含继承的方法)Method[] methods = String.class.getMethods();// 把方法名称提取到一个字符串数组中String[] methodNames = new String[methods.length];for (int i = 0; i < methods.length; i++) {methodNames[i] = methods[i].getName();}// 对方法名称数组进行字母排序Arrays.sort(methodNames);// 为了防止重复打印同一方法名(由于重载会有相同名称),只输出不相同的名称String lastPrinted = "";for (String name : methodNames) {if (!name.equals(lastPrinted)) {System.out.println(name);lastPrinted = name;}}}
}
  • 代码解析:

  • 获取方法:
    调用 String.class.getMethods() 能获取 String 类中所有的公共方法,包括从父类继承的方法。

  • 提取方法名称:
    将所有 Method 对象的名称依次存入 String 数组。

  • 排序:
    使用 Arrays.sort 对数组进行字母排序。

  • 去重输出:
    考虑到方法可能重载导致名称重复,通过比较上一个输出值过滤重复名称后依次打印。

  • 输出结果:(部分)
    在这里插入图片描述

练习2

创建一个包含私有字段private int value的类Secret,通过反射修改该字段的值并验证结果。

  • 代码示例:
    创建两个 Java 文件,一个用于定义类 Secret(中级练习题的目标类),另一个用于编写反射修改私有字段的代码。
// Secret.java
public class Secret {private int value;// 构造方法,用于初始化 valuepublic Secret(int value) {this.value = value;}// 提供 getter 方法便于验证修改后的值public int getValue() {return value;}
}
// ModifySecretReflection.java
import java.lang.reflect.Field;public class ModifySecretReflection {public static void main(String[] args) {try {// 创建 Secret 对象,初始值为 10Secret secret = new Secret(10);System.out.println("修改前的 value: " + secret.getValue());// 通过反射获取私有字段 "value"Field field = Secret.class.getDeclaredField("value");// 解除封装限制,使私有字段可以被操作field.setAccessible(true);// 将字段的值修改为 42field.setInt(secret, 42);// 验证修改后的值System.out.println("修改后的 value: " + secret.getValue());} catch (Exception e) {e.printStackTrace();}}
}
  • 输出结果:
    在这里插入图片描述
  • 代码解析:
  • Secret 类:
    定义了一个私有字段 value,同时提供构造方法和 getter 方法用于初始化和获取该字段的值。
  • 反射修改字段:
    在 ModifySecretReflection 类中:
    • 使用 Secret.class.getDeclaredField(“value”) 得到对应的 Field 对象。
    • 调用 setAccessible(true) 来取消对私有字段的访问限制。
    • 使用 field.setInt(secret, 42) 修改实例 secret 的 value 字段。
  • 验证结果:
    修改前后分别输出 value 字段的值,从而验证反射修改是否成功。

练习3

实现一个简易的“对象拷贝工具”,通过反射将源对象的所有字段值复制到目标对象的同名字段中(支持不同类的兼容字段)。

  • 示例代码:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;public class ObjectCopyUtil {/*** 将 source 对象中所有字段(包括私有字段)复制到 target 对象中同名且类型兼容的字段上。** @param source 源对象* @param target 目标对象*/public static void copyFields(Object source, Object target) {Class<?> srcClass = source.getClass();Class<?> targetClass = target.getClass();// 获取源对象所有字段(包括继承的字段)for (Field srcField : getAllFields(srcClass)) {String fieldName = srcField.getName();try {// 从目标对象中查找同名字段Field targetField = getField(targetClass, fieldName);if (targetField != null) {// 判断目标字段类型是否可以赋值源字段类型(兼容性检查)if (targetField.getType().isAssignableFrom(srcField.getType())) {srcField.setAccessible(true);targetField.setAccessible(true);Object value = srcField.get(source);targetField.set(target, value);}}} catch (Exception e) {// 如果拷贝过程中发生异常,可以打印出来或继续处理e.printStackTrace();}}}/*** 从指定的类及其父类中获取所有字段。** @param clazz 类对象* @return 包含所有字段的数组*/private static Field[] getAllFields(Class<?> clazz) {List<Field> fields = new ArrayList<>();Class<?> current = clazz;while (current != null) {Field[] declaredFields = current.getDeclaredFields();for (Field f : declaredFields) {fields.add(f);}current = current.getSuperclass();}return fields.toArray(new Field[0]);}/*** 在目标类及其父类中查找指定名称的字段。** @param clazz     类对象* @param fieldName 字段名* @return 找到的字段,若无则返回 null*/private static Field getField(Class<?> clazz, String fieldName) {Class<?> current = clazz;while (current != null) {try {return current.getDeclaredField(fieldName);} catch (NoSuchFieldException e) {current = current.getSuperclass();}}return null;}// 下面提供一个简单的测试示例public static void main(String[] args) {// 创建源对象和目标对象,这里两者的字段名称和类型保持兼容SourceClass src = new SourceClass(100, "Hello", 3.14);TargetClass trg = new TargetClass(0, "World", 0.0);System.out.println("拷贝前:");System.out.println("源对象: " + src);System.out.println("目标对象: " + trg);// 调用拷贝工具,将源对象的字段值复制到目标对象中copyFields(src, trg);System.out.println("拷贝后:");System.out.println("源对象: " + src);System.out.println("目标对象: " + trg);}
}// 示例:定义源对象类
class SourceClass {private int num;public String text;protected double decimal;public SourceClass(int num, String text, double decimal) {this.num = num;this.text = text;this.decimal = decimal;}@Overridepublic String toString() {return "SourceClass{num=" + num + ", text='" + text + "', decimal=" + decimal + "}";}
}// 示例:定义目标对象类
class TargetClass {private int num;public String text;protected double decimal;public TargetClass(int num, String text, double decimal) {this.num = num;this.text = text;this.decimal = decimal;}@Overridepublic String toString() {return "TargetClass{num=" + num + ", text='" + text + "', decimal=" + decimal + "}";}
}

输出结果:
在这里插入图片描述

  • 代码解析

  • copyFields 方法:

    • 接受两个参数:源对象和目标对象。
    • 调用 getAllFields 方法获得源对象中所有字段,包括私有和继承的字段。
    • 对每个字段调用 getField,在目标对象中查找同名字段,如果找到则进行类型兼容检查(使用 isAssignableFrom 判断),确保源字段的值能赋给目标字段。
    • 通过 setAccessible(true) 解除访问限制,然后从源对象中获取值,并设置到目标对象中对应字段上。
  • 辅助方法 getAllFields:

    • 通过循环遍历类及其父类,获取所有的声明字段,便于对象拷贝时不遗漏继承字段。
  • 辅助方法 getField:

    • 逐级向上查找目标类中的同名字段,确保可以操作可能在父类中声明的字段。
  • 测试示例:

    • 定义了两个示例类 SourceClass 和 TargetClass,它们具有相同名称和类型的字段。
    • 在 main 方法中创建两个实例,并调用 copyFields 实现拷贝。
    • 最后输出拷贝前后的对象状态,用于验证拷贝效果是否正确。

思考

封装原则与反射

  • 封装的初衷:
    封装主要目的是隐藏内部实现细节,对外只暴露必要的接口,从而降低系统复杂度以及增强代码的安全性和可维护性。通过私有成员和方法,类能更好地控制数据和行为的访问。
  • 反射的特性:
    反射作为一种强大的工具,允许在运行时动态检查类的结构,包括构造方法、字段和方法。通过反射,可以在运行时绕过常规的访问控制,从而对私有成员进行操作。这种能力确实打破了封装的“规则”,但它是为了提供动态性和灵活性,而不是作为日常开发中修改对象状态的一般手段。

合理使用反射“破坏”封装的场景

虽然反射破坏了封装原则,但在某些场景下,这种“破坏”是被认可且必要的:
1.框架与库设计:
很多 Java 框架(如依赖注入、ORM 框架、序列化工具)需要访问对象内部的状态来完成对象构造、初始化、映射和序列化操作。例如,Spring 框架在依赖注入和 AOP 方面就大量利用了反射。

2.单元测试:
在单元测试中,有时需要测试那些没有暴露接口的方法或状态。借助反射可以绕过正常访问限制,对对象进行细粒度的状态验证。不过,过度依赖这种方式可能提示设计上的问题,因此一般建议将测试重点放在公开接口上。

3.调试与诊断:
在调试或诊断复杂系统时,反射可以帮助开发者查看对象的真实内部状态,以便更迅速地定位问题。这种情况下,使用反射有助于快速恢复问题的本质。

4.对象映射和序列化:
对象与数据之间的映射(例如 JSON 序列化与反序列化)通常需要知道对象内部的字段信息。反射可以自动化这一过程,无需手动编写大量冗余代码。

相关文章:

  • 【嵌入式八股4】C++:引用、模板、哈希表与 I/O
  • LeetCode算法题(Go语言实现)_47
  • 操作系统导论——第22章 超越物理内存:策略
  • 基于x86/RK3568电力新能源智能变电站一体化装置
  • CMS 垃圾收集器深度解析
  • 《计算机视觉度量:从特征描述到深度学习》—生成式人工智能在工业检测的应用
  • ceph scrub 导致业务问题优化
  • 【Dify(v1.2) 核心源码深入解析】Agent 模块
  • 深入讲解 CSS 选择器权重及实战
  • 【刷题2025】单指针双指针+滑动窗口+二分法三分法+区间问题
  • 如何一键检查网页里的失效链接和废弃域名?
  • 【加密算法】SM2密钥生成与转换详解:从原理到代码实现
  • ecovadis分为哪些类别,要进行ecovadis认证有什么要求
  • 榕壹云场馆预定系统:基于ThinkPHP+MySQL+UniApp打造的全能运动馆智慧运营解决方案
  • 解锁Grok-3的极致潜能:高阶应用与创新实践
  • 多模态大模型文字识别 vs OCR识别模型
  • 【Python进阶】断言(assert)的十大核心应用场景解析
  • RelativeLayout(相对布局)
  • Mac电脑交叉编译iphone设备可以运行的redsocks, openssl, libsevent
  • Rust + WebAssembly 性能剖析指南
  • “下一个高增长市场,还是中国”,龚正市长会见参加上海车展的国际企业高管
  • 2024年我国数字阅读用户规模达6.7亿
  • 广汽全域赋能,领程皮卡概念车重磅登陆上海车展
  • 南北皆宜的“中国酒都”宿迁:下一程如何更“醇厚绵长”
  • 新华社经济随笔:机器人“摔倒、爬起”的背后
  • 著名电化学家、我国工业电化学奠基人之一郭鹤桐逝世