effective Java 学习笔记(第二弹)

effective Java 学习笔记(第一弹)

整理自《effective Java 中文第3版》

本篇笔记整理第3,4章的内容。

重写equals方法需要注意的地方

  1. 自反性:对于任何非空引用 x,x.equals(x) 必须返回 true。
  2. 对称性:对于任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true。
  3. 传递性:对于任何非空引用 x、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 必须返回 true。
  4. 一致性:对于任何非空引用 x 和 y,如果在 equals 比较中使用的信息没有修改,则 x.equals(y) 的多次调用必须始终返回 true 或始终返回 false。
  5. 对于任何非空引用 x,x.equals(null) 必须返回 false。

在equals方法声明中,不要将参数Object替换成其他类型!可以用instanceof来判断类型是否一致。
重写equals方法时同时也要重写hashcode方法。相等的对象必须要具有相等的哈希码(hash code)。
@EqualsAndHashCode(callSuper = false) 是 Lombok 库中的一个注解,用于自动生成 equals 和 hashCode 方法。这个注解可以帮助开发者减少样板代码的编写。
callSuper = false 参数表示在生成的 equals 和 hashCode 方法中不调用父类的 equals 和 hashCode 方法。这意味着生成的方法将仅基于当前类的字段来实现相等性和哈希值的计算。这样可以确保子类在继承父类时,不会因为父类的 equals 和 hashCode 方法而影响子类的行为。

始终重写toString方法

虽然Object类提供了toString方法的实现,但它返回的字符串是它由类名后跟一个「at」符号(@)和哈希码的无符号十六进制表示组成。若想输出需要的易懂的内容,需要重写。

@Data 注解是 Lombok 库提供的一个注解,用于简化 Java 类的编写。使用 @Data 注解后,Lombok 会自动生成以下内容:

  • 生成 getter 和 setter 方法:为类中的所有字段自动生成 getter 和 setter 方法。
  • 生成 toString 方法:为类生成 toString 方法,包含所有字段的值。
  • 生成 equals 和 hashCode 方法:为类生成 equals 和 hashCode 方法,基于所有字段。
  • 生成 toString 方法:为类生成 toString 方法,包含所有字段的值。
  • 生成无参构造函数:为类生成一个无参构造函数。
  • 生成全参构造函数:为类生成一个包含所有字段的全参构造函数。
import lombok.Data;@Data
public class User {private String name;private int age;private String email;
}

使用 @Data 注解后,Lombok 会为 User 类生成以下内容:

public String getName()
public void setName(String name)
public int getAge()
public void setAge(int age)
public String getEmail()
public void setEmail(String email)
public String toString()
public boolean equals(Object obj)
public int hashCode()
无参构造函数 public User()
全参构造函数 public User(String name, int age, String email)

考虑实现Comparable接口

无论何时实现具有合理排序的值类,你都应该让该类实现Comparable接口,以便在基于比较的集合中轻松对其实例进行排序,搜索和使用。 比较 compareTo 方法的实现中的字段值时,请避免使用「<」和「>」运算符。 相反,使用包装类中的静态compare方法或Comparator接口中的构建方法。
如下是反例,可能导致整形最大长度移除和IEEE754浮点运算失真危险。

static Comparator<Object> hashCodeOrder = new Comparator<>() {public int compare(Object o1, Object o2) {return o1.hashCode() - o2.hashCode();}
};

可以使用如下两种方式替代

static Comparator<Object> hashCodeOrder = new Comparator<>() {public int compare(Object o1, Object o2) {return Integer.compare(o1.hashCode(), o2.hashCode());}
};static Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());

额外话题:IEEE754浮点运算失真
IEEE754浮点运算失真(或精度丢失)是指在使用IEEE754标准表示和计算浮点数时,由于二进制存储和有限位数限制,导致无法精确表示某些十进制小数或运算结果出现微小误差的现象。这是计算机科学中浮点数处理的固有挑战。
核心原因

  1. 二进制与十进制的进制差异
  • 许多十进制小数(如0.1)无法用有限位二进制精确表示(类似1/3无法用有限十进制表示)。
  • 例如,0.1的二进制表示是无限循环小数:0.0001100110011…,存储时会被截断。
  1. IEEE754的存储结构限制
  • 浮点数按三部分存储:符号位、指数位(控制范围)、尾数位(控制精度)。
  • 单精度(32位):1符号位 + 8指数位 + 23尾数位 → 约6-9位有效十进制数字。
  • 双精度(64位):1符号位 + 11指数位 + 52尾数位 → 约15-17位有效十进制数字。
  1. 舍入规则的影响
  • IEEE754默认使用“向最近偶数舍入”(Round to Nearest, Ties to Even),可能导致累积误差。

典型表现

console.log(0.1 + 0.2); // 输出:0.30000000000000004(非精确0.3)
console.log(0.3 - 0.2 === 0.1); // 输出:false
>>> 1.0000000000000001 == 1.0
True  # 双精度无法区分过小的差异

实际影响场景

  • 科学计算:迭代计算中误差累积可能影响结果可靠性。
  • 金融系统:货币计算要求精确到分,浮点误差可能导致账务错误。
  • 游戏物理引擎:微小误差可能引发碰撞检测异常。
    解决方案
  1. 整数替代法
    用整数表示最小单位(如分而不是元):
price_cents = 1000  # 表示10.00元,避免浮点运算
  1. 高精度计算库
    Python:decimal 模块(基于十进制的精确计算):
    from decimal import Decimal
    print(Decimal(‘0.1’) + Decimal(‘0.2’)) # 输出精确0.3
  2. 误差容忍比较
    使用极小值(epsilon)判断近似相等:
function areEqual(a, b, epsilon = 1e-10) {return Math.abs(a - b) < epsilon;
}
  1. 特殊场景处理
    避免超大数与超小数直接相加(会丢失小数部分):
double big = 1e20;
double small = 1.0;
printf("%f\n", big + small - big);  // 输出0.0(small被吞没)

虽然IEEE754的精度问题无法彻底消除,但通过合理的设计(如定点数、符号处理、误差控制)可将其影响降至最低。理解这一机制是开发可靠数值计算程序的关键基础。


使类的成员的可访问性最小化

非零长度的数组总是可变的,所以类具有公共静态 final 数组属性,或返回这样一个属性的访问器是错误的。

如果一个类有这样的属性或访问方法,客户端将能够修改数组的内容。 这是安全漏洞的常见来源:

public static final Thing[] VALUES = { ... };

有两种方法可以解决这个问题。

  1. 可以使公共数组私有并添加一个公共的不可变列表:
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
  1. 可以将数组设置为 private,并添加一个返回私有数组拷贝的公共方法:
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {return PRIVATE_VALUES.clone();
}

应该尽可能地减少程序元素的可访问性(在合理范围内)。 在仔细设计一个最小化的公共 API 之后,你应该防止任何散乱的类,接口或成员成为 API 的一部分。 除了作为常量的公共静态 final 属性之外,公共类不应该有公共属性。 确保 public static final 属性引用的对象是不可变的。

我自己也犯过这样的错误,在某个枚举中创建private static final Set的集合,枚举中加了静态的方法返回这个数组,在外层删除这个集合的某个值,这个集合的值就彻底变了。大致代码如下:

@Getter
public enum VIPTypeEnum {BRONZE(0,"bronze"),SILVER(1,"silver"),GOLD(2,"gold"),SUPPER(999,"supper"),;private int code;private String desc;VIPTypeEnum(int code, String desc) {this.code = code;this.desc = desc;}private static final Set<VIPTypeEnum> showSet = Sets.newHashSet(BRONZE,SILVER,GOLD);public static Set<VIPTypeEnum> getShowSet(){return showSet;}
}
public static void main(String[] args)  {Set<VIPTypeEnum> showSet = VIPTypeEnum.getShowSet();System.out.println(showSet);showSet.remove(VIPTypeEnum.SILVER);System.out.println(VIPTypeEnum.getShowSet());
}

输出如下:

[BRONZE, GOLD, SILVER]
[BRONZE, GOLD]

使用static final修饰的成员变量值改变了。上面说的书中的方法也是可以的:
修改为:

    public static Set<VIPTypeEnum> getShowSet(){return Collections.unmodifiableSet(showSet);}

那么运行main方法,会抛出异常:

[BRONZE, GOLD, SILVER]
Exception in thread "main" 与目标 VM 断开连接, 地址为: ''127.0.0.1:52720',传输: '套接字''
java.lang.UnsupportedOperationExceptionat java.util.Collections$UnmodifiableCollection.remove(Collections.java:1058)at com.example.demo.DemoApplication.main(DemoApplication.java:121)

书中的第二种方法改后会报错:‘clone()’ 在 ‘java.lang.Object’ 中具有 protected 访问权限,因为Set接口并没有定义clone()方法。这通常会导致编译错误,提示无法找到符号或类似的问题。为了解决这个问题,我们可以使用其他方式来复制集合,例如通过构造函数创建一个新的HashSet实例。

    public static Set<VIPTypeEnum> getShowSet(){return new HashSet<>(showSet);}public static void main(String[] args)  {Set<VIPTypeEnum> showSet = VIPTypeEnum.getShowSet();System.out.println(showSet);showSet.remove(VIPTypeEnum.GOLD);System.out.println(showSet);System.out.println(VIPTypeEnum.getShowSet());}

输出结果:

[BRONZE, GOLD, SILVER]
[BRONZE, SILVER]
[BRONZE, GOLD, SILVER]

关于深、浅拷贝的操作,见:Java 对实例进行深拷贝操作

最小化可变性

“最小化可变性”强调设计不可变类(Immutable Class)的重要性。不可变类的实例一旦创建,状态就不可修改,这能显著提升代码的线程安全性、可维护性和可靠性。以下是关键原则及示例:

核心原则

  1. 不提供修改状态的方法(Mutators)
  • 如 setXxx() 方法,禁止直接修改对象属性。
  1. 确保类不可被继承
  • 避免子类破坏不可变性,通常用 final 修饰类或私有化构造函数。
  1. 保护对可变组件的访问
  • 如果类持有可变对象(如数组、集合),需防御性拷贝(Defensive Copy),避免外部修改影响内部状态。

示例1:简单的不可变类

public final class ImmutablePoint {private final int x;private final int y;public ImmutablePoint(int x, int y) {this.x = x;this.y = y;}// 只有getter,没有setterpublic int getX() { return x; }public int getY() { return y; }
}

不可变性体现:

  • x 和 y 被声明为 final,只能在构造函数中初始化。
  • 没有提供修改字段的方法(如 setX())。
  • 类为 final,不允许子类覆盖行为。

示例2:处理深层次可变对象
若类中包含可变对象(如 Date、数组),需确保外部无法修改其内部状态:

public final class ImmutableEvent {private final Date eventDate;  // Date本身是可变的!public ImmutableEvent(Date date) {this.eventDate = new Date(date.getTime());  // 防御性拷贝,避免外部修改原Date}public Date getEventDate() {return (Date) eventDate.clone();  // 返回拷贝,避免外部修改内部Date}
}

构造函数中创建 Date 的副本存储,而非直接引用外界传入的 Date。getEventDate() 返回克隆对象,防止外部通过获取引用修改内部状态。

示例3:Java标准库中的不可变类
String 类:

String s = "Hello";
s = s.concat(" World");  // 返回新对象,原s未被修改

所有看似修改的操作(如 concat()、substring())都返回新对象,原始字符串不变。
BigInteger、BigDecimal:
数值运算(如 add())均返回新实例,确保原有对象不变。

为何不可变类更安全?

  • 线程安全:无需同步,多线程共享时不会出现竞态条件。
  • 缓存友好:可安全复用对象(如 String 常量池)。
  • 防御性拷贝不必要:不可变对象本身无法被修改,传递时无需复制。
  • 可靠的哈希键:作为 HashMap 的键时,哈希值不会改变,避免定位错误。

何时使用可变类?

不可变类的缺点是频繁创建对象可能影响性能,此时可选择可变配套类:String(不可变) ➔ StringBuilder(可变,用于高效拼接字符串)。复杂计算中,若需频繁修改状态,可使用可变对象临时操作,最终生成不可变结果。

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

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

相关文章

mac命令行快捷键

光标移动 Ctrl A: 将光标移动到行首。Ctrl E: 将光标移动到行尾。Option 左箭头: 向左移动一个单词。Option 右箭头: 向右移动一个单词。 删除和修改 Ctrl K: 删除从光标到行尾的所有内容。Ctrl U: 删除从光标到行首的所有内容。Ctrl W: 删除光标前的一个单词。Ctrl …

CentOS 7部署主域名服务器 DNS

1. 安装 BIND 服务和工具 yum install -y bind bind-utils 2. 配置 BIND 服务 vim /etc/named.conf 修改以下配置项: listen-on port 53 { any; }; # 监听所有接口allow-query { any; }; # 允许所有设备查询 3 . 添加你的域名区域配置 …

优化 SQL 语句方向和提升性能技巧

优化 SQL 语句是提升 MySQL 性能的关键步骤之一。通过优化 SQL 语句,可以减少查询时间、降低服务器负载、提高系统吞吐量。以下是优化 SQL 语句的方法、策略和技巧: 一、优化 SQL 语句的方法 1. 使用 EXPLAIN 分析查询 作用:查看 SQL 语句的执行计划,了解查询是如何执行的…

C++ 多线程简要讲解

std::thread是 C11 标准库中用于多线程编程的核心类&#xff0c;提供线程的创建、管理和同步功能。下面我们一一讲解。 一.构造函数 官网的构造函数如下&#xff1a; 1.默认构造函数和线程创建 thread() noexcept; 作用&#xff1a;创建一个 std::thread 对象&#xff0c;但…

Vscode HTML5新增元素及属性

一、‌HTML5 语义化标签 HTML5 语义化标签&#xff08;Semantic Elements&#xff09;是一组 ‌具有明确含义的 HTML 元素‌&#xff0c;通过标签名称直接描述其内容或结构的功能&#xff0c;而非仅作为样式容器&#xff08;如 <div> 或 <span>&#xff09;。它们旨…

【PostgreSQL教程】PostgreSQL 特别篇之 语言接口Python

博主介绍:✌全网粉丝22W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。 感兴趣的可…

Three学习入门(四)

9-Three.js 贴图与材质学习指南 环境准备 <!DOCTYPE html> <html> <head><title>Three.js Texture Demo</title><style> body { margin: 0; } </style> </head> <body><script src"https://cdnjs.cloudflare.…

前端NVM安装

https://v0.dev/chat/settings 本地启动环境 1安装 nvm 2安装node nvm install v18.19.0 nvm install v20.9.0 nvm use 18 node -v 3安装 pnpm npm install -g pnpm 或者 npm i -g pnpm 4启动 代码 目录下 执行 pnpm i pnpm run dev 4.1到代码目录下 4.2直接cmd…

蓝桥杯算法精讲:二分查找实战与变种解析

适合人群&#xff1a;蓝桥杯备考生 | 算法竞赛入门者 | 二分查找进阶学习者 目录 一、二分查找核心要点 1. 算法思想 2. 适用条件 3. 算法模板 二、蓝桥杯真题实战 例题&#xff1a;分巧克力&#xff08;蓝桥杯2017省赛&#xff09; 三、二分查找变种与技巧 1. 查找左边…

cmd命令查看电脑的CPU、内存、存储量

目录 获取计算机硬件的相关信息的命令分别的功能结果展示结果说明获取计算机硬件的相关信息的命令 wmic cpu get name wmic memorychip get capacity wmic diskdrive get model,size,mediaType分别的功能 获取计算机中央处理器(CPU)的名称 获取计算机内存(RAM)芯片的容量…

SCI论文阅读指令(特征工程)

下面是一个SCI论文阅读特征工程V3.0&#xff0c;把指令输入大模型中&#xff0c;并上传PDF论文&#xff0c;就可以帮你快速阅读论文。 优先推荐kimi&#xff0c;当然DeepSeek、QwQ-32B等大语言模型也可以。测试了一下总结的还不错&#xff0c;很详细。 请仔细并深入地阅读所提…

如何监控 SQL Server

监控 SQL Server 对于维护数据库性能、确保数据可用性和最大限度地减少停机时间至关重要。随着企业越来越依赖数据驱动的决策&#xff0c;高效的SQL Server监控策略能显著提升组织生产力和用户满意度。 为什么要监控 SQL Server SQL Server 是许多关键应用程序的支柱&#xf…

python脚本处理excel文件

1.对比perl和python 分别尝试用perl和python处理excel文件&#xff0c;发现perl的比较复杂&#xff0c;比如说read excel就有很多方式 Spreadsheet::Read use Spreadsheet::ParseExcel 不同的method&#xff0c;对应的取sheet的cell方式也不一样。更复杂的是处理含有中文内…

3、pytest实现参数化

在 pytest 中&#xff0c;参数化&#xff08;parametrization&#xff09;是一种强大的功能&#xff0c;可以让你用不同的输入数据重复执行同一个测试函数。这种功能非常有用&#xff0c;可以帮助你显著减少重复代码并提高测试覆盖率。 参数化的主要作用是&#xff1a; 测试多…

Cursor:超强AI变成神器

是一个强大的 AI 编程助手&#xff0c;可以帮助开发者快速地编写、编辑和讨论代码&#xff0c;支持 Python、Java、C# 等多种编程语言&#xff0c;并且可以与 GitHub、Slack 等平台集成。 Cursor 是什么&#xff1f; 想象一下&#xff0c;你有一个能把你的创意变成现实的造梦 …

画秒杀系统流程图

秒杀系统流程图 秒杀系统关键点 高并发处理: 使用网关&#xff08;如 Nginx&#xff09;进行流量限流&#xff0c;避免过载。分布式锁或 Redis 原子操作控制并发。 活动状态检查: Redis 存储活动状态&#xff08;如 seckill:activity:1:status&#xff09;&#xff0c;快速…

【js逆向入门】图灵爬虫练习平台 第九题

地址&#xff1a;aHR0cHM6Ly9zdHUudHVsaW5ncHl0b24uY24vcHJvYmxlbS1kZXRhaWwvOS8 f12进入了debugger&#xff0c;右击选择一律不在此处暂停&#xff0c; 点击继续执行 查看请求信息 查看载荷&#xff0c;2个加密参数&#xff0c;m和tt 查看启动器&#xff0c;打上断点 进来 往…

Vue中的状态管理器Vuex被Pinia所替代-上手使用指南

Pinia.js 是新一代的状态管理器&#xff0c;由 Vue.js团队中成员所开发的&#xff0c;因此也被认为是下一代的 Vuex&#xff0c;即 Vuex5.x&#xff0c;在 Vue3.0 的项目中使用也是备受推崇 Pinia.js 有如下特点&#xff1a; 完整的 typescript 的支持&#xff1b;足够轻量&…

向量数据库学习笔记(1) —— 基础概念

一、 嵌入模型 Embedding Models 嵌入模型是将复杂数据&#xff08;如文本、图像、音频等&#xff09;转换为向量表示的机器学习模型 1. 核心概念 嵌入(Embedding)&#xff1a;将高维、非结构化的数据映射到低维、稠密的向量空间 向量表示&#xff1a;输出固定长度的数值向量…

[NO-WX179]基于springboot+微信小程序的在线选课系统

[NO-WX179]基于springboot微信小程序的在线选课系统 1、管理员角色&#xff08;web端&#xff09;&#xff1a;2、教师角色&#xff08;web端&#xff09;&#xff1a;3、用户角色&#xff08;小程序或web端&#xff09;&#xff1a;4、部分运行截图管理端--教师管理管理端--学…