Effective Java 3rd 条目12 每次都覆写toString

虽然Object提供了一个toString方法的实现,但是它返回的字符串通常不是你的类使用者想看的。它包含了类名,
紧随着一个“at”符号(@)和无符号的哈希码的十六进制表示,比如PhoneNumber@163b91。toString的通用协定说,返回的字符串应该是“一个简洁但是易于读懂的信息表达”。虽然PhoneNumber@163b91是简洁和容易阅读,这可能有争议,但对比707-867-5309, 它不是非常有信息的。toString协定继续说,“所有子类覆写这个方法,这是推荐的”。好的建议,确实!

虽然这不是像遵从equals和hashCode协定(条目10和11)那么关键,提供一个好的toString实现使得你的类使用更加愉悦,而且使得使用这个类的系统更加容易调试。当一个对象被传递到println、printf、字符串连接操作子、断言,或者被调试工具打印,toString方法自动被调用。即使你从未调用对象的toString,其他的人会。比如,一个组件,有一个对你对象的引用,可能在日志错误信息里面包含对象的字符串表示。如果你没有覆写toSTring,信息可能几乎没有用。

如果你已经为PhoneNumber提供了一个好的toString方法,产生一个有用的诊断信息就像如下一样容易:

System.out.println("Failed to connect to " + phoneNumber);

程序员以这种方式产生诊断信息,不管你是否覆写了toString,但是除非你这么做,否则信息将不会有用。提供一个好的toString方法的益处延伸到类的实例之外,至包含这些实例引用的对象,特别是数据集。当打印一个映射{Jenny=PhoneNumber@163b91}或者{Jenny=707-867-5309},你愿意看见哪一个呢?

在实际使用时,toString方法应该返回包含在对象中的所有感兴趣的信息,就像在电话号码例子所示。如果一个对象很大,或者如果它包含状态不有助于字符串表示,这是不实际的。这种情况下,toString应该返回概要,比如Manhattan residential phone directory (1487536 listings)或者Thread[main,5,main]。理想上,字符串是不言自明的。(Thread例子没有达到这个目标)。在它的字符串表示中,未能包含一个对象所有感兴趣的信息的一个非常讨厌的坏处,在于检测失败报告看上去如下:

Assertion failure: expected {abc, 123}, but was {abc, 123}.

当实现toString方法时,你不得不做的一个重要的决定是,在文档上是否要指定返回值的格式。你可以为值类型这么做,比如电话号码或者矩阵,这也是推荐的。指定格式的优势在于它是作为一个标准的、清楚的和人工可读的对象表示。这个表示可以用作输入和输出,而且在持久化人工可读的数据对象,比如CSV文件。如果你指定了格式,那么通常提供一个匹配的静态工厂或者构造子是一个好主意,如此,程序员可以容易地在对象和它的字符串表示之间来回转换。在Java平台库中的许多值类采用这个方法,包括BigInteger、BigDecimal和原始装箱类的大多数。

指定toString返回值的格式的缺点在于,一旦你指定了它,你需要终生坚持它,假设你的类被广泛使用。程序员将编写代码解析这个表示,来生成它,而且把它陷入到持久化数据里面。如果你在未来的发布中改变了这个表示,你讲破坏他们的代码和数据,他们会怒吼。通过不选择指定一个格式,你保持了在后续发布中添加信息或者改进格式的灵活性。

无论你是否指定格式,你应该清楚地文档化你的意图。如果你指定了这个格式,那么你应该精确地这么做。比如,下面是一个toString方法,与条目11的PhoneNumber类相配:

/** * 返回电话号码的字符串表示。

* 字符串包含十二个字符,它的格式是"XXX-YYY-ZZZZ",
* 这个格式中,XXX是地区码,YYY 是前缀,ZZZ是线路号码。
* 每个大写字母代表单个十进制数字。
* 
* 如果这个电话号码的三个部分的任何一个太小了,以致不能填满它的地方,这个地方开头填充零。 
* 比如,如果线路号码的值是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类应该包含地区代码,前缀和线路号码的存取器。如果你不怎么做,你会强迫需要这些信息的程序员解析这个字符串。除了减低性能和造成程序员不必要的工作,这个过程是容易出错的,而且如果你改变了这个格式,会导致脆弱的系统崩溃。未能提供存取器,你把字符串格式改变成一个事实上的API,即使你指定它是可以改变的。

在一个静态效用类中编写一个toString方法是没有任何意义的(条目4)。你也不应该在大多数枚举类型(条目34)中编写toString方法,因为Java为你提供了非常好的一个。然而,你应该在任何抽象类中编写toString方法,这个抽象类的子类共享一个通用的字符串表示。比如,在大多数数据集实现中的toString方法是继承于抽象的数据集类。

谷歌的开源AutoValue框架,就像在条目10讨论的,将会为你产生一个toString方法,大多数IDE也会。这些方法是很好的,告诉你每个域的内容,但是没有指定这个类的意图。因此,比如,为我们的PhoneNumber类(因为电话号码有一个标准的字符串表示)使用一个自动生成的toString方法,这是不合适的,但是对于我们的Potion类,这是完全可接受的。这就是说,相对于继承于Object的那个(它没有告诉你关于对象值的任何事情),自动生成的toString方法是远远可取的。

简要概括,在每个你编写的不可实例化的类中,覆写Object的toString实现,除非一个超类已经这么做了。这使得类使用更加舒服而且辅助了调试。toString方法应该返回简短的有用的对象描述,以一个审美上舒服的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值