Java 基礎語法看似簡單,但細節處往往藏著坑。本文輯錄達內金牌講師唐亮講義精華,聚焦四大高頻考點與實戰陷阱:深克隆三種實現、String/包裝類對象池機制、浮點數比較失真、負數取模奇數判定,附帶標準化代碼模板與最佳實踐。
class Teacher { String name; int age; }
class Student implements Cloneable {
String name; int age; Teacher t;
// 僅調用 super.clone() → 引用拷貝,修改 s2.t 會影響 s1.t
}
class Teacher implements Cloneable {
String name; int age;
Teacher(String name, int age) { this.name = name; this.age = age; }
@Override protected Teacher clone() {
try { return (Teacher) super.clone(); }
catch (CloneNotSupportedException e) { throw new AssertionError(e); }
}
}
class Student implements Cloneable {
String name; int age; Teacher t;
Student(String name, int age, Teacher t) { this.name = name; this.age = age; this.t = t; }
@Override protected Student clone() {
try {
Student s = (Student) super.clone();
s.t = t.clone(); // 關鍵:手動克隆引用對象
return s;
} catch (CloneNotSupportedException e) { throw new AssertionError(e); }
}
}
class Teacher implements Serializable { /* ... */ }
class Student implements Serializable {
String name; int age; Teacher t;
public Student deepClone() throws IOException, ClassNotFoundException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(this);
try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis)) {
return (Student) ois.readObject();
}
}
}
}
前提:整個對象圖所有類必須實現 Serializable,非序列化字段標 transient。
SerializationUtils.clone(obj) (Apache Commons Lang)MapperFacade.clone(obj) (Orika/MapStruct)JSON.parseObject(JSON.toJSONString(obj), Clazz.class) (FastJson/Jackson)String s1 = "hello"; // 常量池獲取/創建
String s2 = "hello"; // 同一對象
String s3 = new String("hello"); // 堆上新建對象
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true
JDK 7+ 常量池移至堆,JDK 8+ 永久代移除,但語義不變:字面量自動進池,new 強制新建。
| 包裝類 | 緩存範圍 | 備註 |
|---|---|---|
| Integer | -128 ~ 127 | 可通過 -XX:AutoBoxCacheMax 調大上限 |
| Short / Byte | -128 ~ 127 | 全範圍緩存 |
| Long | -128 ~ 127 | |
| Character | 0 ~ 127 | |
| Boolean | TRUE / FALSE | 只有兩個實例 |
| Float / Double | 無緩存 | 每次 new |
Integer i1 = 5, i2 = 5; // 自動裝箱 → valueOf(5) → 緩存 → true
Integer i3 = new Integer(5); // 強制新建 → false
System.out.println(i1 == i2); // true
System.out.println(i1 == i3); // false
class Student {
private String name; private int age;
private static final Map<String, Student> POOL = new ConcurrentHashMap<>();
private Student(String name, int age) { this.name = name; this.age = age; }
public static Student newInstance(String name, int age) {
String key = name + "#" + age;
return POOL.computeIfAbsent(key, k -> new Student(name, age));
}
// equals/hashCode 基於 name+age
}
適用於不可變值對象、高頻創建、實例數可控場景(如顏色、坐標、貨幣單位)。
IEEE 754 二進制無法精確表示十進制小數,2.0 - 1.1 實際結果為 0.8999999999999999。
public static boolean equals(double a, double b, double epsilon) {
return Math.abs(a - b) < epsilon;
}
// 使用
if (equals(2.0 - 1.1, 0.9, 1e-10)) { /* 相等 */ }
// 金融/精確計算:強制使用 BigDecimal
BigDecimal a = BigDecimal.valueOf(2.0);
BigDecimal b = BigDecimal.valueOf(1.1);
System.out.println(a.subtract(b).compareTo(BigDecimal.valueOf(0.9)) == 0); // true
public boolean isOdd(int n) {
return n % 2 == 1; // -3 % 2 == -1 → false (錯誤)
}
public boolean isOdd(int n) {
return n % 2 != 0; // -3 % 2 == -1 != 0 → true
// 或位運算(最快)
// return (n & 1) != 0;
}
Java 取模符號遵循被除數(左操作數),-3 % 2 = -1,3 % -2 = 1。判斷奇偶只需關注「餘數是否為 0」。
| 場景 | 錯誤姿勢 | 正確姿勢 |
|---|---|---|
| 深克隆 | super.clone() 不管引用字段 | 手動遞歸 clone / 序列化 / 第三方庫 |
| 字符串比較 | == 比較內容 | equals() 比較內容,== 僅比較引用 |
| 包裝類比較 | == 比較數值 | equals() 或拆箱後 == |
| 浮點數相等 | a == b | Math.abs(a-b) < eps 或 BigDecimal |
| 判斷奇數 | n % 2 == 1 | n % 2 != 0 或 (n & 1) != 0 |
細節決定質量。將這些「反直覺」的語言特性內化為肌肉記憶,配合 SpotBugs、Error Prone、SonarLint 靜態分析工具攔截,能在編碼階段消滅 90% 以上的低級 Bug,讓 Code Review 聚焦於業務邏輯與架構設計。