文章作者:Tyan
博客:noahsnail.com | CSDN | 简书
Item10: 总是重写toString方法
尽管java.lang.Object
提供了toString
方法的实现,但是通常情况下它返回的字符串不是使用类的用户想要的。返回的字符串包含类名,后面是一个@
符号加上哈希码的十六进制表示,例如PhoneNumber@163b91
。toString
的通用约定指出,返回值应该是“简洁但易读的信息表示”[JavaSE6]。虽然可以认为PhoneNumber@163b91
简洁易读,但它与(707) 867-5309
相比,它的信息不够丰富。toString
约定进一步指出,“建议所有的子类重写这个方法”。确实是个好建议。
虽然它不像遵守equals
和hashCode
约定(Item 8, Item 9)那样重要,但是提供一个好的toString
实现可以使你的类用起来更舒适。当对象传到println
,printf
,字符串连接操作符,或assert
中,或通过调试器打印时,会自动调用toString
方法。(Java 1.5版本中平台加入了printf
方法,相关的方法包括String.format
,类似于C语言中的sprintf
方法)。
如果你已经为PhoneNumber
提供了一个好的toString
方法,生成有用的诊断信息是很容易的:
1 | System.out.println("Failed to connect: " + phoneNumber); |
无论你是否重写toString
方法,程序员们都会以这种方式生成诊断信息,但除非你重写了toString
方法,否则这些信息是无用的。提供一个好的toString
方法的好处是除了类的实例之外,也扩展了包含这些实例引用的对象,尤其是集合。当打印一个映射时,{Jenny=PhoneNumber@163b91}
或{Jenny=(707) 867-5309}
你更喜欢哪一个?
当实践时,toString
方法应该返回包含在对象中的所有的感兴趣信息,正如刚才电话号码的例子展示的那样。如果对象很大或它包含不能用字符串表示的状态,重写toString
方法是不切实际的。在这种情况下,toString
应该返回一个概要信息,例如Manhattan white pages (1487536 listings)
或Thread[main,5,main]
。理想情况下,字符串应该是自解释的。(Thread
例子不能满足这样的要求。)
当实现toString
时,你要做的一个重要决定是是否在文档中指定返回值的形式。对于值类建议你这样做,例如电话号码或矩阵。指定返回值形式的优势在于它能为对象提供一个标准的,清晰的,可读的表示。这个表示可以用在输入输出中,也可以用在一致的可读数据对象中,例如XML文档。如果你指定了这个形式,提供一个匹配的静态工厂或构造函数通常是一个好主意,程序员可以很容易地在对象和它的字符串表示之间来回转换。Java平台库中许多值类都采用了这个方法,包括BigInteger
,BigDecimal
和大多数基本类型的包装类。
指定toString
返回值形式的劣势在于一旦你指定了它,假设你的类被广泛使用,你就必须一直坚持它。程序员将会写代码转换这种表示,产生这种形式并将它嵌入到持久化数据中。如果你在将来的版本中更改了表示形式,你将会破坏他们的代码和数据,他们将会抱怨。如果你没有指定一个形式,你保留了添加信息的灵活性或者在后续版本改进这种形式。
无论你决定是否指定格式,你都应该清楚地表明你的意图。如果你指定了格式,你应该准确的去做。例如,下面的Item 9中PhoneNumber
类的toString
方法:
1 | /** |
如果你没有指定格式,文档注释读起来应该如下:
1 | /** |
写代码或持久化数据的依赖于格式细节的程序员,在读了这个文档之后,一旦格式改变,只能自己负责后果。
无论你是否指定了格式,都应该提供toString
返回值中包含的所有信息的程序访问接口。例如,PhoneNumber
类应该包含区域码,前缀和行号的访问器。如果你没有这样做,你会迫使需要这个信息的程序员取转换这个字符串。除了为程序员降低效率和造成不必要的工作之外,这个过程中很容易出错,而且会导致系统非常脆弱,如果你更改了格式系统会崩溃。如果没有提供访问器,即使你指明了字符串格式是可以变化的,这个字符串格式也变成了实际上的API。