虽然 Object 提供了 toString 方法的 一个实现,但它返回的字符串通常并不是类的用户所期望看到的 。 它包含类的名称,以及一个“@”符号,接着是散列码的无符号十六进制表示法,例如 PhoneNumber@163b91 。toString 的通用约定指出,被返回的字符串应该是一个“简洁的但信息丰富,并且易于阅读的表达形式” 。 尽管有人认为PhoneNumber@163b91 算得上是简洁和易于阅读了,但是与 707-867-5309 比较起来,它还算不上是信息丰富的 。 toString 约定进一步指出,“建议所有的子类都覆盖这个方法 。”这是一个很好的建议,真的 !
遵守 toString 约定并不像遵守 equals 和 hashCode 的约定(见第 10 条和第11 条)那么重要,但是,提供好的 toString 实现可以便类用起来更加舒适,使用了这个类的系统也更易于调试 。 当对象被传递给 println 、 printf 、字符串联操作符(+)以及 assert ,或者被调试器打印出来时,toString 方法会被自动调用 。 即使你永远不调用对象的 toString方法,但是其他人也许可能需要 。 例如,带有对象引用的一个组件,在它记录的错误消息中,可能包含该对象的字符串表示法 。 如果你没有覆盖 toString,这条消息可能就毫无用处 。
在实际应用中,toString 方法应该返回对象中包含的所有 值得关注的信息, 例如上述电话号码例子那样 。 如果对象太大,或者对象中包含的状态信息难以用字符串来表达,这样做就有点不切实际 。 在这种情况下, toString 应该返回一个摘要信息。
在实现 toString 的时候,必须要做出 一个很重要的决定 :是否在文档中指定返回值的格式 。 对于值类( value class ),比如电话号码类 、矩阵类 ,建议这么做 。 指定格式的好处是,它可以被用作一种标准的 、明确的、适合人阅读的对象表示法 。 这种表示法可以用于输入和输出,以及用在永久适合人类阅读的数据对象中 。 如果你指定了格式,通常最好再提供一个相匹配的静态工厂或者构造器,以便程序员可以很容易地在对象及其字符串表示法之间来回转换 。Java 平台类库中的许多值类都采用了这种做法,包括Biginteger, BigDecimal 和绝大多数的基本类型包装类( boxed primitive class ) 。
指定 toString 返回值的格式也有不足之处:如果这个类已经被广泛使用,一旦指定格式,就必须始终如一地坚持这种格式 。 程序员将会编写出相应的代码来解析这种字符串表示法、产生字符串表示法,以及把字符串表示法嵌入持久的数据中 。 如果将来的发行版本中改变了这种表示法,就会破坏他们的代码和数据,他们当然会抱怨 。 如果不指定格式,就可以保留灵活性,便于在将来的发行版本中增加信息,或者改进格式 。
无论是否决定指定格式,都应该在文档中明确地表明你的意图 。 如果要指定格式,则应该严格地这样去做 。
无论是否指定格式, 都为 toString 返回值中包含的所有信息提供一种可以通过编程访问之的途径。例如,PhoneNumber 类应该包含针对 area code 、prefix 和line number 的访问方法 。 如果不这么做,就会迫使需要这些信息的程序员不得不自己去解析这些字符串 。除了降低了程序的性能,使得程序员 们去做这些不必要的工作之外,这个解析过程也很容易出错,会导致系统不稳定,如果格式发生变化,还会导致系统崩溃 。 如果没有提供这些访问方法,即使你已经指明了字符串的格式是会变化的,这个字符串格式也成了事实上 的 API 。
在静态工具类中编写 toString 方法是没有意义的 。 也不要在大多数枚举类型中编写 toString 方法,因为 Java 已经为你提供了非常完美的方法 。 但是,在所有其子类共享通用字符串表示法的抽象类中,一定要编写一个 toString方法 。 例如,大多数集合实现中的 toString 方法都是继承自抽象的集合类 。
总而言之,要在你编写的每一个可实例化的类中覆盖 Object 的 toString 实现,除非已经在超类中这么做了 。 这样会使类使用起来更加舒适, 也更易于调试 。toString 方法应该以美观的格式返回一个关于对象的简洁、有用的描述 。