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

Is it really worth implementing toString() for entity classes

提问人:Raedwald 提问时间:2/4/2011 最后编辑:CommunityRaedwald 更新时间:3/31/2016 访问量:11920

问:

始终建议重写(实现)类的方法。toString()

  • Java API 文档本身说“建议所有子类都覆盖此方法”。
  • Bloch,在 Effective Java 中有“Always override toString”项。只有傻瓜才会反驳布洛赫,对吧?

然而,我开始怀疑这个建议:它真的值得为实体类实现吗?toString()


我会试着列出我的推理。

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

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

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

  3. 一个好的表示抓住了对象的本质,所以如果两个表示不同,它们就是不同(不等价)对象的表示,反之,如果两个表示是等价的,它们就是等价对象的表示。这表明以下不变量对于良好的表示(对于非空 xy):

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

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

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

  6. 因此,对于没有数据成员的实体对象来说,这是可以的,但对于大多数类,您希望将它们包含在文本表示中,对吗?事实上,您需要包含所有这些:如果类型具有(非 null)数据成员 x,则希望包含在表示中。Object.toString()x.toString()

  7. 但是,这会给持有对其他实体的引用的数据成员带来问题:即关联。如果一个对象有一个数据成员,那么朴素的实现将生成该人的家谱的片段,而不是它本身的片段。如果存在双向关联,则幼稚的实现将递归,直到堆栈溢出 所以也许跳过持有关联的数据成员?PersonPerson fatherPerson

  8. 但是,具有和数据成员的值类型呢?这些关联应由 .使所有方法都起作用的最简单方法是仅报告 的标识字段 ( 或 )。MarriagePerson husbandPerson wifeMarriage.toString()toString()Person.toString()Person.nameSystem.identityhashCode(this)Person

  9. 因此,对于实体类来说,提供的实现实际上还不错。既然如此,为什么要覆盖它呢?toString()


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

public final class Person {

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

   // more...
}

在调试抛出的 ?toString()IlegalArgumentExceptionPerson.marry()

Java 调试 tostring

评论

1赞 finnw 2/4/2011
我不同意你的观点#3。您并不总是希望在字符串表示形式中显示所有属性。
1赞 Affe 2/4/2011
你对实体这个词使用的正式定义将有助于理解你的论点:)
3赞 Stas 2/4/2011
喜欢你关于布洛赫和傻瓜的声明:)不是说这些家伙很聪明,等等,但只要记住 EJB2.x 中的 EntityBeans 就行了。那是灾难:)一般来说,除非有充分的理由,否则我会避免使用任何“好的做法”。
0赞 bestsss 2/4/2011
>>Bloch,在 Effective Java 中有“Always override toString”项。只有傻瓜才会反驳布洛赫,对吧?<< 把我当傻瓜,没有冒犯!
0赞 Puce 2/4/2011
你是怎么想出#3的?我快速检查了“Effective Java”中的相关部分,但我找不到该声明 - 如果我有......

答:

11赞 Mark Peters 2/4/2011 #1

因此,似乎提供的 toString() 实现实际上对于实体类来说还不错。既然如此,为什么要覆盖它呢?

是什么让你认为目标只是拥有一个唯一的字符串?那不是它的目的。它的目的是为您提供有关实例的上下文,而仅凭类名和哈希码并不能为您提供上下文。toString()

编辑

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

评论

0赞 Raedwald 2/4/2011
“给你背景”:但在我看来,关于实体的信息并不那么有用。与方法中的前提条件检查形成对比,抛出合适的.我建议前提条件是关于值对象和实体的身份或其他方面,在这种情况下,使用异常消息中提供的对实体来说还不错。IlegalArgumentExceptiontoString()
0赞 Mark Peters 2/4/2011
@Raedwald:也许你应该举一个实际的例子。像“实体类”这样的术语有一种松散的倾向。
3赞 Kai Sternad 2/4/2011 #2

在实体类中拥有方法对于调试目的非常有帮助。从实用的角度来看,使用 IDE 模板或类似 Project Lombok 注解的东西可以大大简化这一点,并使其易于快速实现。toString()@ToString

评论

0赞 Raedwald 2/4/2011
“对调试目的非常有帮助”是含糊不清的。您认为实体类方法的哪些方面通常对调试有用?toString()
0赞 Puce 2/4/2011
例如,当您对 JPA 检索到的对象图是否等于预期的对象图进行单元测试时
0赞 Raedwald 2/4/2011
“等于预期”表示唯一相关的关系是 ,对于实体来说,它是 。因此,您所需要的只是一个表示,该表示是否 : 很好地近似于此。x.equals(y)x==ytoString()x==yObject.toString()
3赞 kayahr 2/4/2011 #3

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

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

真。。。就算布洛赫先生这么说......我认为有任何实现 toString() 的规则是没有意义的。它对 hashCode() 和 equals() 有意义,但对 toString() 没有意义。

3赞 hisdrewness 2/4/2011 #4

是的,这是值得的。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 生成器!

评论

0赞 Raedwald 2/4/2011
包含构成对象主键的值(但不包括任何其他值)的原因。我已经提到过“包含该名称或数字 ID 的表示形式”。
11赞 Cowan 2/4/2011 #5

点 #3 是这个论点的薄弱环节,事实上我强烈反对它。您的不变量是(重新排序)

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

我会说,相反:

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

也就是说,逻辑含义。如果 x 和 y 相等,它们的 toString() 应该相等,但相等的 toString() 并不一定意味着对象相等(想想 : 关系;相等的对象必须具有相同的哈希码,但相同的哈希码不能被视为意味着对象相等)。equals()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

你是做什么工作的?你根本不知道发生了什么。如果看到:

IllegalArgumentException: Person["Fred Smith", id=678] cannot marry self
IllegalArgumentException: Person["Mary Smith", id=679] cannot marry self
IllegalArgumentException: Person["Mustafa Smith", id=680] cannot marry self
IllegalArgumentException: Person["Emily-Anne Smith", id=681] cannot marry self

那么你实际上有机会弄清楚发生了什么(“嘿,有人试图让史密斯一家结婚”),这实际上可能有助于调试等。Java 对象 ID 根本不会为您提供任何信息

评论

0赞 Raedwald 2/4/2011
+1 其他人已经谈到了 #3 是错误的,但您的回复是问题最明显的迹象。
0赞 Raedwald 2/4/2011
但是,请注意我的#4和#5:当有“类似ID”的字段时,我似乎很清楚该方法应该显示它们。您的 Smith 系列示例使用 .但是,如果没有这样的字段呢?toString()Person.name
0赞 Cowan 2/4/2011
我想我需要你澄清你所说的“实体”对象是什么意思。如果您谈论的是 EJB 或 JPA 实体,它们总是具有某种 ID 字段,因为根据定义,JPA 实体具有可用于唯一标识它的主键。它是主键,在日志中可能是最有用的......
0赞 Raedwald 2/5/2011
x.equals(y) == (x == y)是这里实体的定义。
0赞 supercat 11/21/2012
@Raedwald:我认为你把“实体”这个术语应用于两种不同的主要事物,其中一种我称之为“可变数据持有者”。可变数据持有者类型的不同实例通常不等价,即使它们包含相同的数据,但是如果这些引用的目标包含相同的数据并且永远不会暴露给任何可能改变它们的东西,则每个对象都包含此类类型的引用,则它们可能是等效的。相比之下,持有对不同实体实例的引用的对象从来都不是等价的。