Java练习——day1(反射)

news2025/4/16 10:47:56

文章目录

  • 练习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;

    // 构造方法,用于初始化 value
    public 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 对象,初始值为 10
            Secret secret = new Secret(10);
            System.out.println("修改前的 value: " + secret.getValue());

            // 通过反射获取私有字段 "value"
            Field field = Secret.class.getDeclaredField("value");
            // 解除封装限制,使私有字段可以被操作
            field.setAccessible(true);
            // 将字段的值修改为 42
            field.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;
    }

    @Override
    public 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;
    }

    @Override
    public 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 序列化与反序列化)通常需要知道对象内部的字段信息。反射可以自动化这一过程,无需手动编写大量冗余代码。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker 安装 Elasticsearch 8.x

Docker 安装 Elasticsearch 8.x 前言一、准备工作二、设置容器的目录结构三、启动一个临时的容器来复制配置文件四、复制配置文件到本地目录五、删除临时容器六、创建并运行容器&#xff0c;挂载本地目录七、修改文件配置监听端口八、端口配置&#xff1a;Host 网络模式 vs Por…

Vue工程化开发脚手架Vue CLI

开发Vue有两种方式 核心包传统开发模式&#xff1a;基于html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工具&#xff08;例如&#xff1a;webpack&#xff09;的环境中开发Vue。 脚手架Vue CLI Vue CLl 是 Vue 官方…

开源智慧巡检——无人机油田AI视频监控的未来之力

油田巡检&#xff0c;关乎能源命脉&#xff0c;却常受困于广袤地形、高危环境和人工效率瓶颈。管道泄漏、设备故障、非法闯入——这些隐患稍有疏忽&#xff0c;便可能酿成大患。传统巡检已无法满足现代油田对安全与效率的需求&#xff0c;而无人机油田巡检系统正以智能化之力重…

Django从零搭建卖家中心登陆与注册实战

在电商系统开发中&#xff0c;卖家中心是一个重要的组成部分&#xff0c;而用户注册与登陆则是卖家中心的第一步。本文将详细介绍如何使用Django框架从零开始搭建一个功能完善的卖家注册页面&#xff0c;包括前端界面设计和后端逻辑实现。 一、项目概述 我们将创建一个名为sel…

MySQL表的使用(4)

首先回顾一下之前所学的增删查改&#xff0c;这些覆盖了平时使用的80% 我们上节课中学习到了MySQL的约束 其中Primary key 是主键约束&#xff0c;我们今天要学习的是外键约束 插入一个表 外键约束 父表 子表 这条记录中classid为5时候&#xff0c;不能插入&#xff1b; 删除…

ollama修改配置使用多GPU,使用EvalScope进行模型压力测试,查看使用负载均衡前后的性能区别

文章目录 省流结论机器配置不同量化模型占用显存1. 创建虚拟环境2. 创建测试jsonl文件3. 新建测试脚本3. 默认加载方式&#xff0c;单卡运行模型3.1 7b模型输出213 tok/s3.1 32b模型输出81 tok/s3.1 70b模型输出43tok/s 4. 使用负载均衡&#xff0c;多卡运行4.1 7b模型输出217t…

Dijkstra算法求解最短路径—— 从零开始的图论讲解(2)

前言 在本系列第一期:从零开始的图论讲解(1)——图的概念,图的存储,图的遍历与图的拓扑排序-CSDN博客 笔者给大家介绍了 图的概念,如何存图,如何简单遍历图,已经什么是图的拓扑排序 按照之前的学习规划&#xff0c;今天笔者将继续带大家深入了解图论中的一个核心问题&#x…

[连载]Transformer架构详解

Transformer: Attention Is All You Need Paper 地址&#xff1a;https://arxiv.org/abs/1706.03762 Paper 代码&#xff1a;https://github.com/tensorflow/tensor2tensor Paper 作者&#xff1a;Ashish Vaswani,Noam Shazeer,Niki Parmar,Jakob Uszkoreit,Llion Jones,Aidan…

LVGL Video控件和Radiobtn控件详解

LVGL Video控件和Radiobtn控件详解 一、 Video控件详解1. 概述2. 创建和初始化3. 基本属性设置4. 视频控制5. 回调函数6. 高级功能7. 注意事项 二、Radiobtn控件详解1. 概述2. 创建和初始化3. 属性设置4. 状态控制5. 组管理6. 事件处理7. 样式设置8. 注意事项 三、效果展示四、…

组合数哭唧唧

前言&#xff1a;手写一个简单的组合数&#xff0c;但是由于长期没写&#xff0c;导致一些细节没处理好 题目链接 #include<bits/stdc.h> using namespace std; #define endl "\n"#define int long longconst int N (int)2e510; const int Mod (int)1e97;int…

NLP高频面试题(四十二)——RAG系统评估:方法、指标与实践指南

1. 引言:RAG系统概述与评估挑战 检索增强生成(Retrieval-Augmented Generation,简称 RAG)是近年来自然语言处理领域的一个重要进展。RAG系统在大型语言模型生成文本的过程中引入了外部检索模块,从外部知识库获取相关信息,以缓解纯生成模型可能出现的幻觉和知识盲点。通过…

Linux路漫漫

目录 Vim模式 基本操作 文本编辑 更多功能 1. 直接启动 Vim 2. 打开一个已存在的文件 3. 打开多个文件 4. 以只读模式打开文件 5. 从指定行号开始编辑 6. 快速打开并执行命令 7. 检查是否安装了 Vim 8. 退出 Vim 前提条件 SCP 命令格式 具体操作 1. Windows 命…

游戏引擎学习第227天

今天的计划 今天的工作重点是进行吸引模式&#xff08;attract mode&#xff09;的开发&#xff0c;主要是处理游戏的进出和其他一些小的细节问题&#xff0c;这些是之前想要整理和清理的部分。我做了一些工作&#xff0c;将游戏代码中的不同部分分离到逻辑上独立的区域&#…

一键直达:用n8n打造谷歌邮箱到Telegram的实时通知流

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 一键直达&#xff1a;用n8n打造谷歌邮箱到Telegram的实时通知流 前言n8n的强大之处实现简便性实战…

【QT】 QT定时器的使用

QT定时器的使用 1. QTimer介绍&#xff08;1&#xff09;QTimer的使用方法步骤示例代码1&#xff1a;定时器的启动和关闭现象&#xff1a;示例代码2&#xff1a;定时器每隔1s在标签上切换图片现象&#xff1a; (2)实际开发的作用 2.日期 QDate(1)主要方法 3.时间 QTime(1)主要方…

【自动化测试】如何获取cookie,跳过登录的简单操作

前言 &#x1f31f;&#x1f31f;本期讲解关于自动化测试函数相关知识介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话…

每天五分钟深度学习PyTorch:RNN CELL模型原理以及搭建

本文重点 RNN Cell(循环神经网络单元)是循环神经网络(RNN)的核心组成部分,用于处理序列数据中的每个时间步,并维护隐藏状态以捕获序列中的时间依赖关系。 RNN CELL的结构 RNN是一个循环结构,它可以看作是RNN CELL的循环,RNN CELL的结构如下图所示,RNN CELL不断进行…

【基于开源insightface的人脸检测,人脸识别初步测试】

简介 InsightFace是一个基于深度学习的开源人脸识别项目,由蚂蚁金服的深度学习团队开发。该项目提供了人脸检测、人脸特征提取、人脸识别等功能,支持多种操作系统和深度学习框架。本文将详细介绍如何在Ubuntu系统上安装和实战InsightFace项目。 目前github有非常多的人脸识…

进程(完)

今天我们就补充一个小的知识点,查看进程树命令,来结束我们对linux进程的学习,那么话不多说,来看. 查看进程树 pstree 基本语法&#xff1a; pstree [选项] 优点&#xff1a;可以更加直观的来查看进程信息 常用选项&#xff1a; -p&#xff1a;显示进程的pid -u&#xff…

【控制学】控制学分类

【控制学】控制学分类 文章目录 [TOC](文章目录) 前言一、工程控制论1. 经典控制理论2. 现代控制理论 二、生物控制论三、经济控制论总结 前言 控制学是物理、数学与工程的桥梁 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、工程控制论 1. 经典…