1. 程式人生 > >Effective Java 第三版——12. 始終重寫 toString 方法

Effective Java 第三版——12. 始終重寫 toString 方法

print ring 並且 love returns 描述 expect rect 方式

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必很多人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到現在已經將近8年的時間,但隨著Java 6,7,8,甚至9的發布,Java語言發生了深刻的變化。
在這裏第一時間翻譯成中文版。供大家學習分享之用。

技術分享圖片

12. 始終重寫 toString 方法

雖然Object類提供了toString方法的實現,但它返回的字符串通常不是你的類的用戶想要看到的。 它由類名後跟一個“at”符號(@)和哈希碼的無符號十六進制表示組成,例如PhoneNumber@163b91。 toString的通用約定要求,返回的字符串應該是“一個簡潔但內容豐富的表示,對人們來說是很容易閱讀的”。雖然可以認為PhoneNumber@163b91

簡潔易讀,但相比於707-867-5309,但並不是很豐富 。 toString通用約定“建議所有的子類重寫這個方法”。好的建議,的確如此!

雖然它並不像遵守equals和hashCode約定那樣重要(條目 10和11),但是提供一個良好的toString實現使你的類更易於使用,並對使用此類的系統更易於調試。當對象被傳遞到println、printf、字符串連接操作符或斷言,或者由調試器打印時,toString方法會自動被調用。即使你從不調用對象上的toString,其他人也可以。例如,對對象有引用的組件可能包含在日誌錯誤消息中對象的字符串表示。如果未能重寫toString,則消息可能是無用的。

如果為PhoneNumber

提供了一個很好的toString方法,那麽生成一個有用的診斷消息就像下面這樣簡單:

System.out.println("Failed to connect to " + phoneNumber);

程序員將以這種方式生成診斷消息,不管你是否重寫toString,但是除非你這樣做,否則這些消息將不會有用。 提供一個很好的toString方法的好處不僅包括類的實例,同樣有益於包含實例引用的對象,特別是集合。 打印map 對象時你會看到哪一個,{Jenny=PhoneNumber@163b91}還是{Jenny=707-867-5309}?

實際上,toString方法應該返回對象中包含的所有需要關註的信息,如電話號碼示例中所示。 如果對象很大或者包含不利於字符串表示的狀態,這是不切實際的。 在這種情況下,toString應該返回一個摘要,如 Manhattan residential phone directory (1487536 listings)

或線程[main,5,main]。 理想情況下,字符串應該是不言自明的(線程示例並沒有遵守這點)。 如果未能將所有對象的值得關註的信息包含在字符串表示中,則會導致一個特別煩人的處罰:測試失敗報告如下所示:

Assertion failure: expected {abc, 123}, but was {abc, 123}.

實現toString方法時,必須做出的一個重要決定是:在文檔中指定返回值的格式。 建議你對值類進行此操作,例如電話號碼或矩陣類。 指定格式的好處是它可以作為標準的,明確的,可讀的對象表示。 這種表示形式可以用於輸入、輸出以及持久化可讀性的數據對象,如CSV文件。 如果指定了格式,通常提供一個匹配的靜態工廠或構造方法,是個好主意,所以程序員可以輕松地在對象和字符串表示之間來回轉換。 Java平臺類庫中的許多值類都采用了這種方法,包括BigInteger,BigDecimal和大部分基本類型包裝類。

指定toString返回值的格式的缺點是,假設你的類被廣泛使用,一旦指定了格式,就會終身使用。程序員將編寫代碼來解析表達式,生成它,並將其嵌入到持久數據中。如果在將來的版本中更改了格式的表示,那麽會破壞他們的代碼和數據,並且還會抱怨。但通過選擇不指定格式,就可以保留在後續版本中添加信息或改進格式的靈活性。

無論是否決定指定格式,你都應該清楚地在文檔中表明你的意圖。如果指定了格式,則應該這樣做。例如,這裏有一個toString方法,該方法在條目 11中使用PhoneNumber類:

/**
 * Returns the string representation of this phone number.
 * The string consists of twelve characters whose format is
 * "XXX-YYY-ZZZZ", where XXX is the area code, YYY is the
 * prefix, and ZZZZ is the line number. Each of the capital
 * letters represents a single decimal digit.
 *
 * If any of the three parts of this phone number is too small
 * to fill up its field, the field is padded with leading zeros.
 * For example, if the value of the line number is 123, the last
 * four characters of the string representation will be "0123".
 */
@Override public String toString() {
    return String.format("%03d-%03d-%04d",
            areaCode, prefix, lineNum);
}

如果你決定不指定格式,那麽文檔註釋應該是這樣的:

/**
 * Returns a brief description of this potion. The exact details
 * of the representation are unspecified and subject to change,
 * but the following may be regarded as typical:
 *
 * "[Potion #9: type=love, smell=turpentine, look=india ink]"
 */
@Override public String toString() { ... }

在閱讀了這條註釋之後,那些生成依賴於格式細節的代碼或持久化數據的程序員,在這種格式發生改變的時候,只能怪他們自己。

無論是否指定格式,都可以通過編程方式訪問toString返回的值中包含的信息。 例如,PhoneNumber類應該包含 areaCode, prefix, lineNum這三個屬性。 如果不這樣做,就會強迫程序員需要這些信息來解析字符串。 除了降低性能和程序員做不必要的工作之外,這個過程很容易出錯,如果改變格式就會中斷,並導致脆弱的系統。 由於未能提供訪問器,即使已指定格式可能會更改,也可以將字符串格式轉換為事實上的API。

在靜態工具類(條目 4)中編寫toString方法是沒有意義的。 你也不應該在大多數枚舉類型(條目 34)中寫一個toString方法,因為Java為你提供了一個非常好的方法。 但是,你應該在任何抽象類中定義toString方法,該類的子類共享一個公共字符串表示形式。 例如,大多數集合實現上的toString方法都是從抽象集合類繼承的。

Google的開放源代碼AutoValue工具在條目 10中討論過,它為你生成一個toString方法,就像大多數IDE工具一樣。 這些方法非常適合告訴你每個屬性的內容,但並不是專門針對類的含義。 因此,例如,為我們的PhoneNumber類使用自動生成的toString方法是不合適的(因為電話號碼具有標準的字符串表示形式),但是對於我們的Potion類來說,這是完全可以接受的。 也就是說,自動生成的toString方法比從Object繼承的方法要好得多,它不會告訴你對象的值。

回顧一下,除非父類已經這樣做了,否則在每個實例化的類中重寫Object的toString實現。 它使得類更加舒適地使用和協助調試。 toString方法應該以一種美觀的格式返回對象的簡明有用的描述。

Effective Java 第三版——12. 始終重寫 toString 方法