Skip to content

Latest commit

 

History

History
76 lines (59 loc) · 6.71 KB

第 12 条:始终要覆盖 toString.md

File metadata and controls

76 lines (59 loc) · 6.71 KB

第 12 条:始终要覆盖 toString

Object类提供的toString方法返回的字符串通常不是你想看到的。 它由后跟“@”符号和哈希代码的无符号十六进制表示,例如,PhoneNumber@163b91。 toString源码里讲到“返回的内容应该简洁且一应俱全,这样利于人类阅读。” 可以说PhoneNumber@163b91这种表现方式是简洁并利与人类阅读的, 与707-867-5309相比,它没有提供太多的信息。源码里还讲到“建议所有子类都重写此方法。” 金玉之言!

与遵守equals和hashcode约定一样重要(第10和11条),提供一个好的toString方法实现使你的类能更方便的进行Debug调试。 这个方法应该当把对象传递给println、printf、字符串连接操作符或断言,或由调试器打印时,自动调用toString方法。 即使你从来没有通过对象调用过此方法。例如,一个组件引用了你的对象可能也包括你的对象中toString方法。这时如果你不重写toString方法的话,toString方法提示的信息可能无济于事。

如果你给PhoneNumber提供一个好的toString方法,生成一个有用的诊断信息是很容易的就像这样:

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

不管是否重写toString,程序员们都将会以这种方式生成诊断信息,但是除非你这样做否则信息不会有什么用。 提供一个好的toString方法延伸到类的实例之外,包含对这些实例的引用,特别是集合。当打印一个map时你想看以下两种表达形式中的哪个?

{Jenny=PhoneNumber@163b91} or {Jenny=707-867-5309}

实际使用中,toString方法应该返回所有的感兴趣的信息包含在这个对象里,如PhoneNumber这个例子所示。如果一个对象特别大或者它包含了一个不利于字符串表示的状态,这样的对象是很不现实的。 在这种情况下,toString方法应该返回一个摘要就像 Manhattan residential phone directory(1487536listings) 或 Thread[main,5,main] 理想上,字符串是不言而明的,未能将对象的所有有趣信息包含在其字符串表现形式中是一个特别烦人的处罚,就像这样:

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方法是没有什么意义的(条目四)。你也没必要在大多数枚举类型中编写toString方法因为Java提供了很好的一个,然而,你应该在每一个抽象类中编写toString方法, 这个抽象类的子类共享一个通用的字符串表现方法。比如,在大多数数据集实现中的toString方法是继承于抽象的数据集类。

谷歌的开源框架AutoValue,就像在条目10中讨论的,将会为你生成一个toString方法,大多数IDE也会这样做。 这些方法很好的告诉你每个字段的内容,但是没有明确的告诉你这个类的意图。所以,比如,为我们的PhoneNumber类使用自动生成的toString方法是不适当的 (作为电话好吗应该有一个标准的字符串表示方法),但是这种方法可能对我们的Potion类是可以接受的。 这就是说,一个自动生成的toString方法远远比继承自Object(没有告诉你关于对象值的任何事情)的可取。

总的来说,在每个你编写的不可实例化的类中,都要重写Object类的toString方法,除非一个超类已经这么做了。 这使得类更加舒适和便与调试。这个toString方法应该返回一个简洁的、有用的对象描述,以一个美观的格式。