始终建议覆盖(实现)类的toString()
方法。
然而,我怀疑这个建议:是否真的值得为实体类实施toString()
?
我会尝试列出我的推理。
实体对象具有唯一标识;即使两个entites具有相同的属性值,它也永远不会与另一个对象相同。也就是说,(对于非null x ),以下不变量适用于实体类(根据定义):
x.equals(y) == (x == y)
toString()
方法返回一个“文本表示”其对象的字符串(用Java API的话说)。
good 表示捕获对象的基本要素,因此如果两个表示不同,则表示不同(非等效)对象的表示,相反,如果两个表示相同,则它们是等价物体的表示。这表明以下是一个良好表示的不变量(对于非null 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()
在表示中。
但是这会对包含对其他实体的引用的数据成员产生问题:即 association 。如果Person
对象具有Person father
数据成员,则天真实现将生成该人的家谱的片段,而不是Person
本身。如果存在双向关联,那么天真的实现将recurse until you get stack overflow所以可能会跳过持有关联的数据成员?
但是,具有Marriage
和Person husband
数据成员的值类型Person wife
呢?这些协会应该由Marriage.toString()
报告。使所有toString()
方法有效的最简单方法是Person.toString()
仅报告Person.name
的标识字段(System.identityhashCode(this)
或Person
)。
因此,toString()
提供的实现似乎对实体类来说实际上并不算太糟糕。在那种情况下,为什么要覆盖它呢?
为了使其具体,请考虑以下代码:
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()
会有多大用处?
答案 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()。