第10条:始终要覆盖toString

虽然java.lang.Object提供了toString方法的一个实现,但它返回的字符串通常不是类的用户所期望看到的。它包含类的名称,以及一个“@”符号,接着是散列码的无符号十六进制表示法。例如“PhoneNumber@163b91”。toString的通用约定之处,被返回的字符串应该是一个“简洁的,但信息丰富,并且易于阅读的的表达形式”[JavaSE6]。尽管有人认为“PhoneNumber@163b91”算得上是简洁和易于阅读了,但是与“(707)867-5309”比较起来,它还算不上是信息丰富的。toString的约定进一步之处,“建议所有的子类都覆盖这个方法。”这是一个很好的建议,真的!

虽然遵守toString的约定并不像遵守equalshashCode的约定(见第8条和第9条)那么重要,但是,提供好的toString实现可以使类用起来更加舒适。当对象被传递给printlnprintf、字符串联操作符(+)以及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平台类库中的许多值类都采用了这种做法,包括BigIntegerBigDecimal和绝大多数苏的基本类型包装类(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。

results matching ""

    No results matching ""