虽然 Object 类提供了 toString 方法的实现,但它返回的字符串通常不是使用类的用户想要看到的。 它由类名后跟一个「at」符号(@)和哈希码的无符号十六进制表示组成,例如 PhoneNumber@163b91
,这对使用它的人来说实际意义不大。 toString 的通用约定要求,返回的字符串应该是「一个简洁但内容丰富的表示,对人们来说是很容易阅读的」。虽然可以认为 PhoneNumber@163b91
简洁易读,但相比于直接返回字符串: 707-867-5309
,高下立判 。 toString 通用约定**「建议所有的子类重写这个方法」**。
虽然它并不像遵守 equals 和 hashCode 约定那样重要 (Item 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}
?
Manhattan residential phone directory (1487536 listings)
或线程[main,5,main]
。Assertion failure: expected {abc, 123}, but was {abc, 123}.
指定 toString 返回值的格式的缺点是,假设你的类被广泛使用,一旦指定了格式,就会终身使用。程序员将编写代码来解析表达式,生成它,并将其嵌入到持久数据中。如果在将来的版本中更改了格式的表示,那么会破坏他们的代码和数据。但通过选择不指定格式,就可以保留在后续版本中添加信息或改进格式的灵活性。
无论是否决定指定格式,你都应该清楚地在注释文档中表明你的意图。如果指定了格式,则应该这样做。例如,这里有一个 toString 方法,该方法在条目 11 中使用 PhoneNumber
类:
/** *返回此电话号码的字符串表示形式。 *12个字符,格式为 “XXX-YYY- zzzz”,其中XXX为区号,YYY为前缀,ZZZZ是编号。每个字符代表一个十进制数字。 *如果这个电话号码的三个部分中的任何一个太小填充它的字段,字段是用前导零填充的。 *例如,如果行号为123,则最后部分的四个字符的字符串表示将是"0123" */ @Override public String toString() { return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum); }
如果你决定不指定格式,那么文档注释应该是这样的:
/** * 返回方法实现简要描述. * 该实现的具体细节是不确定的,可能会改变,但下面的例子可能比较典型: * "[Potion #9: type=love, smell=turpentine, look=india ink]" * 即打印出每个属性的值 */ @Override public String toString() { ... }
无论是否指定格式,都可以通过编程方式访问 toString 返回的值中包含的信息。 例如,PhoneNumber
类应该包含 areaCode, prefix, lineNum 这三个属性。 如果不这样做,就会强迫程序员需要这些信息来解析字符串。 除了降低性能和程序员做不必要的工作之外,这个过程很容易出错,如果改变格式就会中断,并导致脆弱的系统。 由于未能提供访问器,即使已指定格式可能会更改,也可以将字符串格式转换为事实上的 API。
在静态工具类(详见Item4)中编写 toString 方法是没有意义的,因为我们根本没考虑实例化静态工具类。 你也不应该在大多数枚举类型(Item 34)中写一个 toString 方法,因为 Java 为你提供了一个非常好的方法。 但是,你应该在任何抽象类中定义 toString 方法,该类的子类共享一个公共字符串表示形式。 例如,大多数集合实现上的 toString 方法都是从抽象集合类继承的。
Google 的开放源代码 AutoValue 工具在条目 10 中讨论过,它为你生成一个 toString 方法,就像大多数 IDE 工具一样(例如IDEA在重写toString方法时会帮我们默认实现)。 这些方法非常适合告诉你每个属性的内容,但并不是专门针对类的含义。 因此,例如,为我们的 PhoneNumber
类使用自动生成的 toString 方法是不合适的(因为电话号码具有标准的字符串表示形式),但是对于我们的 Person
类来说,这是完全可以接受的。 也就是说,自动生成的 toString 方法比从 Object 继承的方法要好得多,它不会告诉你对象的值。
总之,除非父类已经重写了toString,否则在每个需要实例化的类中重写 Object 的 toString 实现。 它使得类更加方便使用和调试。 toString 方法应该以一种美观的格式返回对象的简明有用的描述。