是否真的值得为实体类实现toString()

时间:2011-02-03 18:28:26

标签: java debugging tostring

始终建议覆盖(实现)类的toString()方法。

  • Java API documentation本身说“建议所有子类都覆盖此方法。”。
  • Bloch,在 Effective Java 中有“Always override toString”项。只有傻瓜才与布洛赫相矛盾,对吗?

然而,我怀疑这个建议:是否真的值得为实体类实施toString()


我会尝试列出我的推理。

  1. 实体对象具有唯一标识;即使两个entites具有相同的属性值,它也永远不会与另一个对象相同。也就是说,(对于非null x ),以下不变量适用于实体类(根据定义):

    x.equals(y) == (x == y)

  2. toString()方法返回一个“文本表示”其对象的字符串(用Java API的话说)。

  3. good 表示捕获对象的基本要素,因此如果两个表示不同,则表示不同(非等效)对象的表示,相反,如果两个表示相同,则它们是等价物体的表示。这表明以下是一个良好表示的不变量(对于非null x y ):

    x.toString().equals(y.toString()) == x.equals(y)

  4. 因此对于我们期望的实体 x.toString().equals(y.toString()) == (x == y) 也就是说,每个实体对象应该具有唯一的文本表示,toString()返回。某些实体类将具有唯一名称或数字ID字段,因此其toString()方法可以返回包含该名称或数字ID的表示。但一般来说,toString()方法无法访问此类字段。

  5. 如果没有实体的唯一字段,toString()可以做的最好的事情就是包含一个对不同对象不太可能相同的字段。但这正是System.identityHashCode()的要求,这是Object.toString()提供的。

  6. 所以Object.toString()对于没有数据成员的实体对象是可以的,但是对于大多数类,你希望将它们包含在文本表示中,对吗?实际上,您希望包含所有:如果类型具有(非空)数据成员 x ,您可能希望包含x.toString()在表示中。

  7. 但是这会对包含对其他实体的引用的数据成员产生问题:即 association 。如果Person对象具有Person father数据成员,则天真实现将生成该人的家谱的片段,而不是Person本身。如果存在双向关联,那么天真的实现将recurse until you get stack overflow所以可能会跳过持有关联的数据成员?

  8. 但是,具有MarriagePerson husband数据成员的值类型Person wife呢?这些协会应该由Marriage.toString()报告。使所有toString()方法有效的最简单方法是Person.toString()仅报告Person.name的标识字段(System.identityhashCode(this)Person)。

  9. 因此,toString()提供的实现似乎对实体类来说实际上并不算太糟糕。在那种情况下,为什么要覆盖它呢?


  10. 为了使其具体,请考虑以下代码:

    public final class Person {
    
       public void marry(Person spouse)
       {
          if (spouse == this) {
             throw new IlegalArgumentException(this + " may not marry self");
          }
          // more...
       }
    
       // more...
    }
    

    在调试toString()引发的IlegalArgumentException时,覆盖Person.marry()会有多大用处?

5 个答案:

答案 0 :(得分:10)

  

所以看起来提供的toString()实现对于实体类来说实际上并不算太糟糕。在那种情况下,为什么要覆盖它?

是什么让你认为toString()的目标只是拥有一个独特的字符串?这不是它的目的。它的目的是为您提供有关实例的上下文,并且只是类名和哈希码不会为您提供上下文。

修改

只是想说我绝不认为您需要在每个对象上覆盖toString()。无值对象(如侦听器或策略的具体实现)无需覆盖toString(),因为每个实例都与其他实例无法区分,这意味着类名就足够了。

答案 1 :(得分:9)

第3点是这个论点中的薄弱环节,事实上我对此持不同意见。你的不变量是(重新排序)

x.equals(y) == x.toString().equals(y.toString()); 

我会说,而不是:

x.equals(y) → x.toString().equals(y.toString()); 

即,逻辑含义。如果x和y相等,它们的toString()应该相等,但是等于toString()必然意味着对象是相等的(想想equals():{{ 1}}关系;相等的对象必须具有相同的哈希码,但相同的哈希码可以来表示对象是相等的。)

从根本上说,hashCode()在程序意义上并没有任何“意义”,而且我认为你试图用一个来填充它。 toString()作为日志等工具最有用;你问一下被覆盖的toString()有多大用处:

toString()

我会说大量有用。假设您在日志中发现了很多错误,请参阅:

throw new IlegalArgumentException(this + " may not marry self");
你怎么了?你根本不知道发生了什么。如果你看到:

IllegalArgumentException: com.foo.Person@1234ABCD cannot marry self
IllegalArgumentException: com.foo.Person@2345BCDE cannot marry self
IllegalArgumentException: com.foo.Person@3456CDEF cannot marry self
IllegalArgumentException: com.foo.Person@4567DEFA cannot marry self
然后你真的有机会弄清楚发生了什么('嘿,有人试图让史密斯家族自己结婚')这实际上可能有助于调试等.Java对象ID给你没有信息在所有

答案 2 :(得分:3)

在实体类中使用toString()方法对于调试目的非常有用。从实际的角度来看,使用IDE模板或Project Lombok @ToString注释之类的东西可以简化这一过程,并且可以很容易地快速实现。

答案 3 :(得分:3)

是的,这是值得的。 ToString有助于为对象的状态提供有形的视觉输出。 IMO,它在实体中尤其重要,因为ORM或其他第三方库经常将对象打印为其日志记录策略的一部分。

logger.debug("Entity: {}", entity);

显然会隐式调用toString()。

它帮助我一次又一次地在视觉上看到实体的状态,以确定它在事务性方面是暂时的还是持久的,只是一般的调试。

你愿意看到这个:

DEBUG | pattern: test.entity.MyEntity@12345f

或者这个:

DEBUG | pattern: MyEntity [id = 1234.0, foo=bar, bar=baz]

简而言之,你不能覆盖toString的唯一原因是懒惰。在最近的版本中,Eclipse甚至有一个toString生成器!

答案 4 :(得分:2)

我总是将toString()用于我自己的目的,而不是因为某些技术要求。当我有一个Person类时,toString方法返回该人的名字。不多也不少。它不是唯一的,但出于调试目的,它足以看出人的意思。特别是在Web开发中,当我只需要在JSP中编写对象名来获取人的名字时,这非常方便,所以我知道我有正确的对象。

如果对象有一些唯一的数据(如数据库ID),那么这是toString()的完美候选者,因此它可以返回#294: John Doe。但独特性不是必需品。

真的......即使布洛赫先生这么说......我认为实施toString()没有任何规则是有意义的。它对hashCode()和equals()有意义,但不适用于toString()。