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.pswp.cn/pingmian/73497.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/73497.shtml
英文地址,請注明出處:http://en.pswp.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、部分運行截圖管理端--教師管理管理端--學…