首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 程式設計師常犯的 5 大錯誤與最佳實踐

     編輯:Java 相關知識

資深 Java 開發者總結的五大高發錯誤:Null 濫用、異常吞噬、並發修改異常、違反契約、原始類型誤用。本文深度解析每類錯誤的成因、危害、修正方案與現代 Java 最佳實踐,助你寫出更健壯、可維護的代碼。

錯誤一:Null 的過度使用

問題場景


List<String> accountIds = person.getAccountIds();
for (String accountId : accountIds) {
    processAccount(accountId);
}

當 getAccountIds() 返回 null 時,增強 for 循環拋出 NullPointerException。

最佳實踐

1. 返回空集合而非 null


public List<String> getAccountIds() {
    return accounts != null ? accounts : Collections.emptyList();
}

2. 使用 Optional (Java 8+)


// 返回 Optional
public Optional<List<String>> getAccountIds() {
    return Optional.ofNullable(accounts);
}

// 調用端
person.getAccountIds().ifPresent(ids -> {
    ids.forEach(this::processAccount);
});

// 或鏈式處理單值
Optional<String> opt = Optional.ofNullable(nullableString);
opt.ifPresent(System.out::println);  // Java 8 風格
// opt.ifPresentOrElse(...);        // Java 9+ 支援 else 分支

優勢:編譯期強制處理空值、語義明確、避免 NPE、支援函數式鏈式操作。

錯誤二:忽略異常

問題代碼


try {
    selfie.show();
} catch (NullPointerException e) {
    // 什麼都不做,假裝沒事發生
}

危害

  • 錯誤被靜默吞噬,排查極度困難
  • 系統處於未知狀態,可能導致數據不一致
  • 違反「快速失敗」原則

正確處理策略

  1. 記錄日誌:log.error("操作失敗", e);
  2. 轉換拋出:包裝為業務異常 throw new BusinessException("顯示失敗", e);
  3. 用戶提示:友好錯誤訊息而非堆疊追蹤
  4. 註釋說明:若確需忽略,必須註釋理由
    
    try {
        selfie.delete();
    } catch (NullPointerException unimportant) {
        // 允許刪除不存在的對象,無需處理
    }
    

錯誤三:並發修改異常

觸發場景


List<IHat> hats = new ArrayList<>();
hats.add(new Ushanka()); // 有耳罩
hats.add(new Fedora());
hats.add(new Sombrero());

// 錯誤:遍歷時直接移除
for (IHat hat : hats) {
    if (hat.hasEarFlaps()) {
        hats.remove(hat);  // ConcurrentModificationException!
    }
}

五種解決方案對比

方案代碼示例優點缺點
收集後再刪除
List<IHat> toRemove = new ArrayList<>();
for (IHat hat : hats) {
    if (hat.hasEarFlaps()) toRemove.add(hat);
}
hats.removeAll(toRemove);
簡單直觀額外記憶體
Iterator.remove()
Iterator<IHat> it = hats.iterator();
while (it.hasNext()) {
    if (it.next().hasEarFlaps()) it.remove();
}
標準做法、無額外集合僅支援刪除
ListIterator
ListIterator<IHat> it = hats.listIterator();
while (it.hasNext()) {
    if (it.next().hasEarFlaps()) {
        it.set(new Sombrero()); // 替換
    }
}
支援 add/set/remove 完整操作僅適用 List
Stream 過濾 (Java 8+)
hats = hats.stream()
    .filter(hat -> !hat.hasEarFlaps())
    .collect(Collectors.toCollection(ArrayList::new));
函數式、不可變風格、線程安全生成新列表、大數據量記憶體壓力
List.removeIf (Java 8+)
hats.removeIf(IHat::hasEarFlaps);
最簡潔、底層用 Iterator.remove僅支援刪除
並發集合
List<IHat> hats = new CopyOnWriteArrayList<>();
// 可安全遍歷時修改
無需同步、遍歷不拋異常寫入複製開銷大、適合讀多寫少

Stream 陷阱:遍歷流時修改原集合


// 錯誤!
List<IHat> filtered = hats.stream()
    .peek(hat -> { if (hat.hasEarFlaps()) hats.remove(hat); })
    .collect(Collectors.toList());

// 正確:先過濾收集,再賦值
hats = hats.stream()
    .filter(hat -> !hat.hasEarFlaps())
    .collect(Collectors.toCollection(ArrayList::new));

錯誤四:違反契約

hashCode 與 equals 契約

核心規則:

  1. 若 a.equals(b) == true,則 a.hashCode() == b.hashCode() 必須成立
  2. 若 hashCode() 相等,equals() 可真可偽

破壞契約的後果


public static class Boat {
    private String name;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Boat boat = (Boat) o;
        return Objects.equals(name, boat.name);
    }
    
    @Override
    public int hashCode() {
        return (int) (Math.random() * 5000); // 每次調用返回隨機值!
    }
}

// 測試
Set<Boat> boats = new HashSet<>();
boats.add(new Boat("Enterprise"));
System.out.println(boats.contains(new Boat("Enterprise"))); // 極大概率 false!

結果:HashSet、HashMap、HashTable 等基於雜湊的集合完全失效,無法正確查找、去重。

finalize 契約誤用

  • finalize() 由 GC 調用,調用時間不可預測
  • 不應依賴它釋放關鍵資源(檔案句柄、資料庫連接、鎖)
  • 正確做法:實作 AutoCloseable,使用 try-with-resources

錯誤五:使用原始類型而非參數化類型

問題代碼


List list = new ArrayList();  // 原始類型
list.add(10);
list.add("Twenty");           // 編譯通過,運行時炸彈
list.forEach(n -> System.out.println((Integer)n * 2)); // ClassCastException!

修正版本


List<Integer> list = new ArrayList<>(); // 參數化類型
list.add(10);
list.add("Twenty");  // 編譯錯誤:類型不匹配,提前發現問題

為什麼要泛型?

  • 編譯期類型檢查,將運行時錯誤前移到編譯期
  • 消除強制轉型,代碼更清晰安全
  • 文檔化 API 契約

總結速查表

錯誤類型核心對策關鍵 API/語法
Null 濫用返回空集合/OptionalCollections.emptyList(), Optional
異常吞噬記錄/轉換/說明log.error, 自定義異常
並發修改Iterator/Stream/並發集合Iterator.remove(), removeIf, CopyOnWriteArrayList
違反契約正確實作 equals/hashCodeObjects.equals, Objects.hash, IDE 自動生成
原始類型始終使用泛型List<T>, Map<K,V>

避開這五大坑,你的 Java 代碼將更加健壯、專業、易維護。建議配合 SpotBugs、SonarQube 等靜態分析工具,在 CI 流程中自動檢測這類問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved