Java教程

Effective Java---12 始终重写 toString 方法

本文主要是介绍Effective Java---12 始终重写 toString 方法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

虽然 Object 类提供了 toString 方法的实现,但它返回的字符串通常不是使用类的用户想要看到的。 它由类名后跟一个「at」符号(@)和哈希码的无符号十六进制表示组成,例如 PhoneNumber@163b91,这对使用它的人来说实际意义不大。 toString 的通用约定要求,返回的字符串应该是「一个简洁但内容丰富的表示,对人们来说是很容易阅读的」。虽然可以认为 PhoneNumber@163b91 简洁易读,但相比于直接返回字符串: 707-867-5309,高下立判 。 toString 通用约定**「建议所有的子类重写这个方法」**。

为什么重写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}?

重写注意点

  1. 实际上,toString 方法应该返回对象中包含的所有需要关注的信息,如电话号码示例中所示。
    如果对象很大或者包含不利于字符串表示的状态,这是不切实际的。 在这种情况下,toString 应该返回一个摘要,如 Manhattan residential phone directory (1487536 listings) 或线程[main,5,main]
    理想情况下,字符串应该是不言自明的(线程示例并没有遵守这点)。
    如果未能将所有对象的值得关注的信息包含在字符串表示中,则会导致一个特别烦人的问题:测试失败报告如下所示 :
Assertion failure: expected {abc, 123}, but was {abc, 123}.
  1. 实现 toString 方法时,必须要注意的是:在注释文档中指定返回值的格式。 建议你对值类进行此操作,例如电话号码或矩阵类。 指定格式的好处是它可以作为标准的,明确的,可读的对象表示。 这种表示形式可以用于输入、输出以及持久化可读性的数据对象,如 CSV 文件。 如果指定了格式,通常提供一个匹配的静态工厂或构造方法,是个好主意,所以程序员可以轻松地在对象和字符串表示之间来回转换。 Java 平台类库中的许多值类都采用了这种方法,包括 BigInteger,BigDecimal 和大部分基本类型包装类。

指定 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 方法应该以一种美观的格式返回对象的简明有用的描述

这篇关于Effective Java---12 始终重写 toString 方法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!