我正在使用Eclipse生成.equals()
和.hashCode()
,并且有一个选项标记为"使用'instanceof'来比较类型".默认情况下,此选项可以取消选中并用于.getClass()
比较类型.有什么理由我应该喜欢.getClass()
过instanceof
?
不使用instanceof
:
if (obj == null) return false; if (getClass() != obj.getClass()) return false;
使用instanceof
:
if (obj == null) return false; if (!(obj instanceof MyClass)) return false;
我通常检查instanceof
选项,然后进入并删除" if (obj == null)
"检查.(这是多余的,因为空对象总是会失败instanceof
.)有什么理由不好吗?
Josh Bloch喜欢你的方法:
我赞成这种
instanceof
方法的原因是,当你使用这种getClass
方法时,你有一个限制,即对象只等于同一个类的其他对象,相同的运行时类型.如果扩展一个类并为它添加几个无害的方法,那么检查一下子类的某个对象是否等于超类的对象,即使对象在所有重要方面都相同,你也会得到令人惊讶的答案是他们并不平等.事实上,这违反了对利斯科夫替代原则的严格解释,并且可能导致非常令人惊讶的行为.在Java中,它特别重要,因为大多数集合(HashTable
等等)基于equals方法.如果您将超类的成员作为键放在哈希表中,然后使用子类实例查找它,您将找不到它,因为它们不相等.
另见这个SO答案.
有效的Java 第3章也涵盖了这一点.
如果你使用instanceof
,使你的equals
实现final
将保留方法的对称契约:x.equals(y) == y.equals(x)
.如果final
看起来有限制,请仔细检查您的对象等效概念,以确保您的重写实现完全维护Object
该类建立的合同.
Angelika Langers 平等的秘密通过对一些常见和众所周知的例子进行了长时间的详细讨论,其中包括Josh Bloch和Barbara Liskov,他们发现了大多数问题.她还进入了instanceof
VS getClass
.有人引用它
结论
解剖了四个任意选择的equals()实现的例子,我们得出什么结论?
首先:在equals()的实现中,有两种截然不同的方式来执行类型匹配检查.类可以通过instanceof运算符允许超类和子类对象之间的混合类型比较,或者类可以通过getClass()测试将不同类型的对象视为不相等.上面的示例很好地说明了使用getClass()的equals()实现通常比使用instanceof的实现更强大.
instanceof测试仅对最终类是正确的,或者至少方法equals()在超类中是最终的.后者基本上意味着没有子类必须扩展超类的状态,但只能添加与对象的状态和行为无关的功能或字段,例如瞬态或静态字段.
另一方面,使用getClass()测试的实现始终符合equals()契约; 他们是正确和强大的.但是,它们在语义上与使用instanceof测试的实现非常不同.使用getClass()的实现不允许比较子类和超类对象,即使子类没有添加任何字段,甚至不想重写equals().这样一个"普通的"类扩展例如是在为这个"普通"目的而定义的子类中添加调试打印方法.如果超类通过getClass()检查禁止混合类型比较,那么普通扩展将无法与其超类相比.这是否完全取决于类的语义和扩展的目的.
使用的原因getClass
是为了确保equals
合同的对称性.来自equals的JavaDocs:
它是对称的:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true.
通过使用instanceof,可能不是对称的.考虑一下这个例子:Dog extends Animal.动物equals
做的instanceof
动物的检查.狗的equals
做一个instanceof
犬检查.给动物a和狗d(与其他领域相同):
a.equals(d) --> true d.equals(a) --> false
这违反了对称属性.
为了严格遵循平等契约,必须确保对称性,因此阶级需要相同.
这是一场宗教辩论.两种方法都存在问题.
使用instanceof,您永远不能将重要成员添加到子类.
使用getClass并违反Liskov替换原则.
Bloch在Effective Java Second Edition中有另一个相关的建议:
第17项:继承的设计和文件或禁止它
如果我错了,请纠正我,但是当你想确保你的实例不是你要比较的类的子类时,getClass()会很有用.如果你在那种情况下使用instanceof你不能知道,因为:
class A { } class B extends A { } Object oA = new A(); Object oB = new B(); oA instanceof A => true oA instanceof B => false oB instanceof A => true // <================ HERE oB instanceof B => true oA.getClass().equals(A.class) => true oA.getClass().equals(B.class) => false oB.getClass().equals(A.class) => false // <===============HERE oB.getClass().equals(B.class) => true
如果您想确保只有该类可以匹配,请使用getClass() ==
。如果要匹配子类,则instanceof
需要。
同样,instanceof将不会与null匹配,但可以安全地与null进行比较。因此,您不必进行空检查。
if ( ! (obj instanceof MyClass) ) { return false; }
这取决于您是否考虑给定类的子类是否等于其父类。
class LastName { (...) } class FamilyName extends LastName { (..) }
在这里我将使用'instanceof',因为我希望将姓氏与FamilyName进行比较
class Organism { } class Gorilla extends Organism { }
在这里,我将使用“ getClass”,因为该类已经说过这两个实例并不等效。