核心契約(JDK 文檔規範)
toString() 返回對象的字串表示。結果應該是簡潔、信息豐富、易於閱讀的。建議所有子類覆寫此方法。
- 預設實現(Object):
getClass().getName() + '@' + Integer.toHexString(hashCode())
- 調試生命線:
System.out.println(obj)、日誌框架、IDE 變數檢視、異常堆棧自動調用
標準實現模式
1. 手寫標準格式
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", roles=" + roles +
'}';
}
2. Objects.toString() 輔助(Java 7+)
@Override
public String toString() {
return "User{" +
"id=" + id +
", username=" + Objects.toString(username) + // null 安全
", email=" + Objects.toString(email) +
'}';
}
3. StringJoiner / String.format(Java 8+)
@Override
public String toString() {
return new StringJoiner(", ", User.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("username=" + username)
.add("email=" + email)
.toString();
}
4. Lombok @ToString(生產首選)
@ToString
public class User {
private Long id;
private String username;
private String email;
@ToString.Exclude // 排除敏感/遞歸字段
private String password;
}
5. IDE 生成(IntelliJ: Alt+Insert → toString)
可選模板:默認、StringBuilder、StringJoiner、JSON 風格
特殊場景處理
| 場景 | 問題 | 解決方案 |
| 循環引用 | 棧溢出 | @ToString.Exclude、手寫只輸出 ID、使用 IdentityHashSet 追蹤 |
| 大集合/大對象 | 輸出過長、效能差 | 截斷顯示(前 100 字元)、只輸出 size、延遲載入 |
| 敏感字段 | 密碼、Token 洩露 | @ToString.Exclude、手寫掩碼 *** |
| 異步日誌 | toString() 成為熱點 | 懶評估:實現 CharSequence、僅在實際輸出時構建字串 |
| 緩存 toString | 不可變對象重複調用 | 構造時預計算、或用 Guava MoreObjects.toStringHelper() |
效能深度分析
String 拼接的歷史演進
// 1. 古老:+ 拼接 → 編譯器生成 StringBuilder
// 2. 手寫 StringBuilder(單線程最優)
// 3. StringJoiner(Java 8,語意清晰,內部用 StringBuilder)
// 4. String.format(可讀性最好,但解析格式字串有開銷)
// 基準測試:100萬次調用
// StringBuilder: ~15ms
// StringJoiner: ~18ms
// String.format: ~120ms
// + 拼接: ~20ms (現代 JDK 優化後接近 StringBuilder)
StringBuffer vs StringBuilder
StringBuffer:同步、線程安全、效能低,**幾乎無使用場景**
StringBuilder:非同步、單線程首選、JDK 5+ 引入
- toString() 內部調用本質單線程,**永遠用 StringBuilder**
StringBuilder/Buffer + toString() 經典轉換模式
// StringBuffer → String(舊代碼常見)
StringBuffer sb = new StringBuffer("hello");
String s = sb.toString(); // 必須調用 toString(),PrintStream 無重載接受 StringBuffer
// 現代鏈式調用
String s = new StringBuilder()
.append("user:")
.append(user.getName())
.append(", id:")
.append(user.getId())
.toString();
實戰:高效能懶評估 toString
public class LazyToString implements CharSequence {
private final Supplier supplier;
private volatile String cached;
public LazyToString(Supplier supplier) {
this.supplier = supplier;
}
@Override public String toString() {
String c = cached;
if (c == null) {
synchronized (this) {
c = cached;
if (c == null) cached = c = supplier.get();
}
}
return c;
}
// CharSequence 接口方法委託給 toString()
@Override public int length() { return toString().length(); }
@Override public char charAt(int index) { return toString().charAt(index); }
@Override public CharSequence subSequence(int start, int end) { return toString().subSequence(start, end); }
}
// 使用
public class HeavyObject {
private final List hugeList = ...;
public CharSequence toStringLazy() {
return new LazyToString(() ->
"HeavyObject{listSize=" + hugeList.size() + ", first=" + hugeList.get(0) + "}"
);
}
}
最佳實踐總結
- 必須覆寫:所有實體類、DTO、VO、異常類
- 包含關鍵標識字段:ID、業務鍵、狀態
- 排除敏感/遞歸/大字段:密碼、雙向關聯、巨大集合
- 統一格式風格:專案統一用 Lombok @ToString 或 IDE 模板
- 不可變類預計算:構造時生成 toString 緩存
- 日誌框架配合:Log4j2/Logback 支持 {%toString} 佔位符、懶評估
總結
toString() 是對象的「身份證」。優秀的實現能讓調試事半功倍,糟糕的實現(預設、循環引用、敏感洩露、效能殺手)則會讓排查變成噩夢。**推薦:Lombok @ToString + 排除敏感字段 + 統一專案模板**,兼顧開發效率、安全性、可讀性。
- 上一篇文章: Java String 核心方法深度解析:構造、轉換、查找、切割、替換、比較實戰指南
- 下一篇文章: Java 深拷貝完整方案:序列化 vs JSON 兩種實現與效能對比
Copyright ©
程式碼之家 All Rights Reserved