第10条:始终要覆盖toString
虽然java.lang.Object
提供了toString
方法的一个实现,但它返回的字符串通常不是类的用户所期望看到的。它包含类的名称,以及一个“@”符号,接着是散列码的无符号十六进制表示法。例如“PhoneNumber@163b91
”。toString
的通用约定之处,被返回的字符串应该是一个“简洁的,但信息丰富,并且易于阅读的的表达形式”[JavaSE6]。尽管有人认为“PhoneNumber@163b91
”算得上是简洁和易于阅读了,但是与“(707)867-5309”比较起来,它还算不上是信息丰富的。toString
的约定进一步之处,“建议所有的子类都覆盖这个方法。”这是一个很好的建议,真的!
虽然遵守toString
的约定并不像遵守equals
和hashCode
的约定(见第8条和第9条)那么重要,但是,提供好的toString
实现可以使类用起来更加舒适。当对象被传递给println
、printf
、字符串联操作符(+
)以及assert
或者被调试器打印出来时,toString
方法会被自动调用。(Java 1.5发行版本在平台中增加了printf
方法,还提供了包括String.format
的相关方法,与C语言中的sprint
相似。)
如果为PhoneNumber
提供了好的toString
方法,那么,要产生有用的诊断消息会非常容易:
System.out.println("Failed to connect: " + phoneNumber);
不管是否覆盖了toString
方法,程序员都将以这种方式来产生诊断消息,但是如果没有覆盖toString
方法,产生的消息将难以理解。提供好的toString
方法,不仅有益于这个类的实例,同样也有益于那些包含这些实例的引用的对象,特别是集合对象。打印Map
时有下面这两条消息:“Jenny=PhoneNumber@163b91
”和“Jenny=(408)867-5309
”,你更愿意看到哪一个?
在实际应用中,toString
方法应该返回对象中包含的所有值得关注的信息,譬如上述电话号码例子那样。如果对象太大,或者对象中包含的状态信息难以用字符串来表达,这样做就有点不切实际。在这种情况下,toString
应该返回一个摘要信息,例如“Manhattan white pages (1487536 listings)
”或者“Thread[main, 5, main]
”。理想情况下,字符串应该是自描述的(self-explanatory),(Thread
例子不满足这样的要求。)
在实现toString
的时候,必须要做出一个很重要的决定:是否在文档中指定返回值的格式。对于值类(value class),比如电话号码类、矩阵类,也建议这么做。指定格式的好处是,它可以被用作一种标准的、明确的、适合人阅读的对象表示法。这种表示法可以用于输入和输出,以及用在永久的适合人类阅读的数据对象中。例如XML文档。如果你指定了格式,最后再提供一个相匹配的静态工厂或者构造器,以便程序员可以很容易地在对象和它的字符串表示法之间来回转换。Java平台类库中的许多值类都采用了这种做法,包括BigInteger
、BigDecimal
和绝大多数苏的基本类型包装类(boxed primitive class)。
指定toString
返回值的格式也有不足之处:如果这个类已经被广泛使用,一旦指定格式,就必须始终如一地坚持这种格式。程序员将会编写出相应的代码来解析这种字符串表示法、产生字符串表示法,以及把字符串表示法嵌入到持久的数据中。如果将来的发行版本中改变了这种表示法,就会破坏他们的代码和数据,他们当然会抱怨。如果不指定格式,就可以保留灵活性,便于在将来的发行版本中增加信息,或者改进格式。
无论你是否决定指定格式,都应该在文档中明确的表明你的意图。如果你要指定格式,则应该严格地这样去做。例如,下面是第9条中PhoneNumber
类的toString
方法:
/**
* Returns the string representation of this phone number.
* The string consists of fourteen 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".
*
* Note that there is a single space separating the closing
* parenthesis after the area code from the first digit of the
* prefix.
*/
@Override public String toString() {
return String.format("(%03d) %03d-%04d",
areaCode, prefix, lineNumber);
}
如果你绝低挡不指定格式,那么文档注释部分也应该有如下所示的指示信息:
/**
* Returns a brief description of this potion. The exact details
* of the representation are unspecified and subject to change,
*/
@Override public String toString() { ... }
对于那些依赖于格式的细节进行编程或者产生永久数据的程序猿,在读到这段注释之后,一旦格式被改变,则只能自己承担后果。
无论是否指定格式,都为toString
返回值中包含的所有信息,提供一种编程式的访问途径。例如,PhoneNumber
类应该包含针对area code、prefix和line number的访问方法。如果不这么做,就会迫使那些需要这些信息的程序员不得不自己去解析这些字符串。除了降低了程序的性能,使得程序员们去做这些不必要的工作之外,这个解析过程也很容易出错,会导致系统不稳定,如果格式发生变化,还会导致系统崩溃。如果没有提供这些访问方法,即使你已经指明了字符串的格式是可以变化的,这个字符串格式也成了事实上的API。