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

发布于 2024-10-16 01:37:02 字数 2628 浏览 3 评论 0原文

一贯建议重写(实现)类的 toString() 方法。

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

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


我将尝试阐述我的推理。

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

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

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

  3. 良好的表示捕获了对象的本质,因此如果两个表示不同,则它们是不同(不等价)对象的表示,反之,如果两个表示相等,则它们是等价的表示对象。这表明以下不变量可以很好地表示(对于非空 xy):

    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. 但这会给保存对其他实体的引用的数据成员带来问题:也就是说,它们是关联。如果 Person 对象具有 Personfather 数据成员,则简单的实现将生成该人的家谱的片段,而不是 Person 本身的片段。如果存在双向关联,则简单的实现将递归直到堆栈溢出,因此可能会跳过包含的数据成员关联?

  8. 但是,如果值类型 Marriage 具有 Person doctorPersonwife 数据成员呢?这些关联应该由 Marriage.toString() 报告。使所有 toString() 方法正常工作的最简单方法是让 Person.toString() 仅报告身份字段 (Person.namePersonSystem.identityhashCode(this))。

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


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

public final class Person {

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

   // more...
}

在调试由 Person.marry()< 抛出的 IlegalArgumentException 时,覆盖 toString() 会有多大用处? /代码>?

It is consistently advised to override (implement) the toString() method of a class.

  • The Java API documentation itself says "It is recommended that all subclasses override this method.".
  • Bloch, in Effective Java has the item "Always override toString". And only a fool contradicts Bloch, right?

I am however coming to doubt this advice: is it really worth implementing toString() for entity classes?


I'll try to lay out my reasoning.

  1. An entity object has a unique identity; it is never the same as another object, even if the two entites have equivalent attribute values. That is, (for non-null x), the following invariant applies for an entity class (by definition):

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

  2. The toString() method returns a string that "textually represents" its object (in the words of the Java API).

  3. A good representation captures the essentials of the object, so if two representations are different they are representaions of different (non-equivalent) objects, and conversely if two represenations are equivalent they are representations of equivalent objects. That suggests the following invariant for a good representation (for non-null x, y):

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

  4. Thus for entities we expect
    x.toString().equals(y.toString()) == (x == y)
    that is, each entity object should have a unique textual representation, which toString() returns. Some entity classes will have a unique name or numeric ID field, so their toString() method could return a representation that includes that name or numeric ID. But in general, the toString() method does not have access to such a field.

  5. Without a unique field for an entity, the best that toString() can do is to include a field that is unlikely to be the same for different objects. But that is exactly the requirement of System.identityHashCode(), which is what Object.toString() provides.

  6. So Object.toString() is OK for an entity object that has no data members, but for most classes you would want to include them in the text representation, right? In fact, you'd want to include all of them: if the type has a (non null) data member x, you would want to include x.toString() in the representation.

  7. But this creates a problem for data members that hold references to other entities: that is, which are associations. If a Person object has a Person father data member, the naive implementation will produce a fragment of that person's family tree, not of the Person itself. If there are two-way assocaitions, a naive implementation will recurse until you get stack overflow So maybe skip the data members that hold associations?

  8. But what about a value type Marriage having Person husband and Person wife data members? Those associations ought to be reported by Marriage.toString(). The simplest way to make all the toString() methods work is for Person.toString() to report only the identity fields (Person.name or System.identityhashCode(this)) of the Person.

  9. So it seems that the provided implementation of toString() is actually not too bad for entity classes. In that case, why override it?


To make it concrete, consider the following code:

public final class Person {

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

   // more...
}

Just how useful would an override of toString() be when debugging an IlegalArgumentException thrown by Person.marry()?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

嗼ふ静 2024-10-23 01:37:02

第三点是这个论点的薄弱环节,事实上我强烈不同意它。 你的不变量是(重新排序的)

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

我想说的是,

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

:即逻辑蕴涵。如果 x 和 y 相等,则它们的 toString() 应该相等,但等于 toString() 不一定意味着对象相等(想想 equals()) code>: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 给您不完全没有信息

Point # 3 is the weak link in this argument, and I in fact violently disagree with it. Your invariant is (reordered)

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

I would say, rather:

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

That is, logical implication. If x and y are equal, their toString()s should be equal, but an equal toString() does not necessarily mean that the objects are equal (think of the equals():hashCode() relationship; equal objects must have the same hash code, but same hash code can not be taken to mean the objects are equal).

Fundamentally, toString() doesn't really have any 'meaning' in a programmatic sense, and I think you're trying to imbue it with one. toString() is most useful as a tool for logging etc; you ask how useful an overridden toString() would be given:

throw new IlegalArgumentException(this + " may not marry self");

I would say it's massively useful. Say you notice a lot of errors in your logs and see:

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

what do you do? You have no idea at all what's going on. If you see:

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

then you actually have some chance of working out what's going on ('Hey, someone is trying to make the Smith family marry themselves') and that may actually help with debugging etc. Java object IDs give you no information at all.

猫烠⑼条掵仅有一顆心 2024-10-23 01:37:02

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

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

编辑

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

So it seems that the provided implementation of toString() is actually not too bad for entity classes. In that case, why override it?

What makes you think the goal of toString() is simply to have a unique String? That's not its purpose. Its purpose is to give you context about the instance, and simply a classname and hashcode don't give you context.

Edit

Just want to say that I by no means think you need to override toString() on every object. Valueless objects (like a concrete implementation of a listener or a strategy) need not override toString() since every single instance is indistinguishable from any other, meaning the class name is sufficient.

吃兔兔 2024-10-23 01:37:02

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

Having a toString() method in entity classes can be tremendously helpful for debugging purposes. From a practical standpoint, using IDE templates or something like the Project Lombok @ToString annotation simplifies this a lot and makes it easy to implement quickly.

少钕鈤記 2024-10-23 01:37:02

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

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

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

I always use toString() for my own purposes and not because of some technical requirement. When I have a Person class then the toString method returns the name of the person. No more, no less. It's not unique but for debugging purposes it is enough to see what person is meant. Especially in web development this comes in very handy when I just have to write the object name in JSPs to get the name of the person so I know I have the correct object.

If the object has some unique data (like a database ID) then this is a perfect candidate for toString() so it could return #294: John Doe. But uniqueness isn't a requirement.

Really... Even if Mister Bloch says so... I don't think it makes sense to have any rules for implementing toString(). It makes sense for hashCode() and equals() but not for toString().

花落人断肠 2024-10-23 01:37:02

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

Yes it's worth it. ToString helps give tangible, visual output to the state of the object. IMO, it's especially important in an entity, because ORMs or other third party libraries print an object as a part of their logging strategy quite frequently.

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

will obviously implicitly call toString().

It has helped me time and time again to visually see the state of the entity to determine whether or not it's transient or persistent in the logging in terms of transactionality, and just general debugging.

Would you rather see this:

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

or this:

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

In short, the only reason you wouldn't override toString is laziness. In recent releases, Eclipse even has a toString generator!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文