是否真的值得为实体类实现 toString()
一贯建议重写(实现)类的 toString()
方法。
- Java API 文档 本身说“建议所有子类重写此方法。”。
- Bloch,在Effective Java中有一项“Always override toString”。只有傻瓜才会反驳布洛赫,对吧?
然而,我开始怀疑这个建议:真的值得为实体类实现 toString() 吗?
我将尝试阐述我的推理。
实体对象具有唯一的标识;它永远不会与另一个对象相同,即使这两个实体具有相同的属性值。也就是说,(对于非空 x),以下不变量适用于实体类(根据定义):
x.equals(y) == (x == y)
toString()
方法返回一个“以文本方式表示”其对象的字符串(用 Java API 的话说)。良好的表示捕获了对象的本质,因此如果两个表示不同,则它们是不同(不等价)对象的表示,反之,如果两个表示相等,则它们是等价的表示对象。这表明以下不变量可以很好地表示(对于非空 x、y):
x.toString().equals(y.toString()) == x.equals(y)
因此对于我们期望的实体
x.toString().equals(y.toString()) == (x == y)
也就是说,每个实体对象都应该有一个唯一的文本表示,由toString()
返回。某些实体类将具有唯一的名称或数字 ID 字段,因此它们的 toString() 方法可以返回包含该名称或数字 ID 的表示形式。但一般来说,toString()
方法无法访问这样的字段。如果实体没有唯一字段,
toString()
所能做的最好的事情就是包含一个对于不同对象不太可能相同的字段。但这正是System.identityHashCode()
,这是Object.toString()
提供的。因此,
Object.toString()
对于没有数据成员的实体对象来说是可以的,但对于大多数类,您希望将它们包含在文本表示中,对吧?事实上,您希望包含它们的全部:如果该类型有一个(非空)数据成员x,您会希望包含x。表示中的 toString()
。但这会给保存对其他实体的引用的数据成员带来问题:也就是说,它们是关联。如果
Person
对象具有Personfather
数据成员,则简单的实现将生成该人的家谱的片段,而不是Person
本身的片段。如果存在双向关联,则简单的实现将递归直到堆栈溢出,因此可能会跳过包含的数据成员关联?但是,如果值类型
Marriage
具有Person doctor
和Personwife
数据成员呢?这些关联应该由Marriage.toString()
报告。使所有toString()
方法正常工作的最简单方法是让Person.toString()
仅报告身份字段 (Person.name
或Person
的System.identityhashCode(this)
)。看来提供的 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.
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)
The
toString()
method returns a string that "textually represents" its object (in the words of the Java API).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)
Thus for entities we expect
x.toString().equals(y.toString()) == (x == y)
that is, each entity object should have a unique textual representation, whichtoString()
returns. Some entity classes will have a unique name or numeric ID field, so theirtoString()
method could return a representation that includes that name or numeric ID. But in general, thetoString()
method does not have access to such a field.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 ofSystem.identityHashCode()
, which is whatObject.toString()
provides.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 includex.toString()
in the representation.But this creates a problem for data members that hold references to other entities: that is, which are associations. If a
Person
object has aPerson father
data member, the naive implementation will produce a fragment of that person's family tree, not of thePerson
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?But what about a value type
Marriage
havingPerson husband
andPerson wife
data members? Those associations ought to be reported byMarriage.toString()
. The simplest way to make all thetoString()
methods work is forPerson.toString()
to report only the identity fields (Person.name
orSystem.identityhashCode(this)
) of thePerson
.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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
第三点是这个论点的薄弱环节,事实上我强烈不同意它。 你的不变量是(重新排序的)
我想说的是,
:即逻辑蕴涵。如果 x 和 y 相等,则它们的 toString() 应该相等,但等于 toString() 不一定意味着对象相等(想想 equals()) code>:
hashCode()
关系;相等的对象必须具有相同的哈希码,但相同的哈希码不能表示对象相等)。从根本上讲,
toString()
在程序意义上并没有任何“意义”,我认为你正试图向它注入一种意义。toString()
作为日志记录等工具最有用;你问重写的toString()
会有多大用处:我会说它非常有用。假设您注意到日志中有很多错误,然后看看:
您会做什么?你根本不知道发生了什么。如果您看到:
那么您实际上有机会弄清楚正在发生的事情(“嘿,有人试图让史密斯家族与自己结婚”),这实际上可能有助于调试等。Java 对象 ID 给您不完全没有信息。
Point # 3 is the weak link in this argument, and I in fact violently disagree with it. Your invariant is (reordered)
I would say, rather:
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 overriddentoString()
would be given:I would say it's massively useful. Say you notice a lot of errors in your logs and see:
what do you do? You have no idea at all what's going on. If you see:
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.
是什么让您认为
toString()
的目标只是拥有一个唯一的字符串?这不是它的目的。它的目的是为您提供有关实例的上下文,而仅仅类名和哈希码并不能为您提供上下文。编辑
只是想说,我绝不认为您需要在每个对象上重写
toString()
。无价值的对象(如侦听器或策略的具体实现)不需要重写 toString() ,因为每个实例都与其他实例无法区分,这意味着类名就足够了。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 overridetoString()
since every single instance is indistinguishable from any other, meaning the class name is sufficient.在实体类中拥有 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.我总是出于自己的目的而使用 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().
是的,这是值得的。 ToString 有助于为对象的状态提供有形的、可视化的输出。 IMO,这在实体中尤其重要,因为 ORM 或其他第三方库经常打印对象作为其日志记录策略的一部分。
显然会隐式调用 toString()。
它一次又一次地帮助我直观地查看实体的状态,以确定它在事务性日志记录中是暂时的还是持久的,以及一般调试。
您愿意看到这个:
还是这个:
简而言之,您不会重写 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.
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:
or this:
In short, the only reason you wouldn't override toString is laziness. In recent releases, Eclipse even has a toString generator!