我正在尝试编写一些通用代码,以根据字段列表定义类相等性和哈希码。在编写我的equals方法时,我想知道,基于Java约定,两个不同的对象是否应该是相同的。让我举几个例子;
class A {
int foo;
}
class B {
int foo;
}
class C extends A {
int bar;
}
class D extends A {
void doStuff() { }
}
...
A a = new A(); a.foo = 1;
B b = new B(); b.foo = 1;
C c = new C(); c.foo = 1; c.bar = 2;
D d = new D(); d.foo = 1;
a.equals(b); //Should return false, obviously
a.equals(c);
c.equals(a); //These two must be the same result, so I'd assume it must be false, since c cant possible equal a
a.equals(d); //Now this one is where I'm stuck.
我认为没有理由在最后一个例子中两者不应该相等,但它们确实有不同的类。有人知道会议的规定吗?如果它们相等,那么equals方法应如何处理呢?
编辑:如果有人对这个问题背后的代码感兴趣,请参阅:https://gist.github.com/thomaswp/5816085这有点脏,但我欢迎对这个要点发表评论。
答案 0 :(得分:21)
他们可以,但在这种情况下维护the symmetric and transitive properties of equality通常非常困难。至少在有一个有用/直观的平等定义时。
如果允许子类认为自己等于超类的实例,则超类需要认为自己等于子类的实例。这意味着你将在超类中编码关于子类(所有可能的子类?)的特定知识,并根据需要进行向下转换,这不是很干净。
或者,您仅使用A
中包含的字段进行比较,并且根本不会覆盖equals()
。这解决了上述问题,但问题是C
的两个bar
值不同的实例被认为是相等的,这可能不是您想要的。
或者,您在C
中覆盖,如果另一个对象是bar
的实例,则比较C
,但对A
的实例不进行比较,你有另一个问题。 c1.equals(c2)
将是错误的,但c1.equals(a)
将是真的,c2.equals(a)
也是如此a.equals(c2)
。这打破了传递性(因为c1 == a
和a == c2
暗示c1 == c2
)。
总之,理论上可行,但你必须削弱你的平等实现才能这样做。此外,运行时类是一个对象的属性,就像bar
一样,所以我希望具有不同具体类的对象彼此不相等无论如何。 / p>
答案 1 :(得分:4)
首先注意:当您覆盖.equals()
时,您绝对必须覆盖.hashCode()
,并遵守defined contract。这不是开玩笑。如果你不遵守这一点,你注定会遇到问题。
至于处理彼此继承的不同类之间的相等性,如果所有这些类都有一个共同的成员,则可以这样做:
@Override
public int hashCode()
{
return commonMember.hashCode();
}
@Override
public boolean equals(final Object o)
{
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof BaseClass))
return false;
final BaseClass other = (BaseClass) o;
return commonMember.equals(other.commonMember); // etc -- to be completed
}
答案 2 :(得分:1)
Object.equals()
必须在多个调用中具有反身性,对称性,传递性,一致性,并且x.equals(null)
必须为false。除此之外没有其他要求。
如果您定义的类的equals()
执行了所有这些操作,则它是可接受的equals()
方法。除了你自己提供的那个问题之外,它应该是多么精细的问题没有答案。你需要问自己:我想要哪些对象是平等的?
但是,请注意,当a.equals(b)
和a
是不同类的实例时,您应该有充分的理由使b
为true,因为这可能会使实现正确的方法变得棘手两个班级都equals()
。
答案 3 :(得分:1)
另请记住,您需要遵循这些规则才能正确实现equals方法。
Andrzej Doyle正确地说,当它跨越多个类时,很难实现Symetric和Transitive属性。
答案 4 :(得分:1)
在我看来,这个问题表明了一个泥泞的架构。从理论上讲,如果你想实现.equals,你只需比较两个实例中的特定成员就可以做到这一点,但这是否是一个好主意实际上取决于这些类的目的是什么目的(即使那时我认为有更好的方法。)
这些对象或多或少只是打算成为数据包吗?如果是这样,也许你应该创建一个单独的比较类来确定这两个对象是否足够“等同于”你需要的目的,而不是强迫对象本身关心一些外来的,无关的类。如果我的代码与可能不相关的对象本身有关,我会担心,因为我认为由于暂时的便利而让他们了解对方可能是个好主意。此外,正如Andrzej所提到的,父类知道或关心派生类的具体实现细节是非常有问题的。我亲眼看到这会导致细微和极端的问题。
对象是“行动者”而不是数据存储?既然你的子类D实现了一个方法,那么这表明它不仅仅是一个数据包...在这种情况下,从哲学上讲,我看不出如何考虑A和D等于仅仅基于它是一个好主意一组值字段。比较这些字段,是的。认为它们相同或等同?不,这听起来像是长期的可维护性噩梦。
以下是我认为更好的想法的例子:
class A implements IFoo{
private int foo;
public int getFoo(){ return foo; }
}
class B implements IFoo{
private int foo;
public int getFoo(){ return foo; }
}
class CompareFoos{
public static boolean isEquivalent(IFoo a, IFoo b){
// compare here as needed and return result.
}
}
IFoo a = new A();
IFoo b = new B();
boolean result = CompareFoos.isEquivalent(a, b);
答案 5 :(得分:0)
在某种程度上,这取决于您希望代码执行的操作。只要你清楚equals方法将比较什么,那么我不会发现问题。我认为当你开始制作许多子类(例如E类)时,问题才真正到来。有一个危险,那就是其中一个子类不遵守合同,所以你最终可能会
a.equals(e) --> true
e.equals(a) --> false
会导致奇怪的行为。
就个人而言,我试图避免等于比较两个不同的类并返回true但我已经完成了几次整个类层次结构在我的控制和最终之下。
答案 6 :(得分:0)
考虑这个例子 -
abstract class Quadrilateral{
Quadrilateral(int l, int w){
length=l;
width=w;
}
private int length, width;
}
class Rectangle extends Quadrilateral{
Rectangle(int l, int w){super(l,w);}
}
class Square extends Quadrilateral{
Square(int l){super(l, l);}
}
Square s = new Square(3);
Rectangle r = new Rectangle(3,3);
r.equals(s);//This should be true because the rectangle and square are logically the same.
所以是的,有些情况下两个不同的类可以相等。虽然显然这并不常见。
答案 7 :(得分:0)
没有
Set<Parent> p = new HashSet<>();
p.insert(new Parent(1));
p.insert(new Child(1));
假设这两个实例是equals
,p
是否包含Child
?很不清楚。更有趣:
class Parent {
public int foo = 0;
void foo() {
foo = 1;
}
}
class Child extends Parent {
@Override
void foo() {
foo = 2;
}
}
Set<Parent> set = new HashSet<>();
for(Parent parent : set) {
parent.foo();
System.out.println(parent.foo); // 1 or 2?
}
我要求您了解p
的元素包含的内容,而不会在Set
,HashSet
或equals
的Javadoc页面上花费超过1分钟。
答案 8 :(得分:0)
简短的回答是,在非常有限的情况下(例如Integer
和Long
),不同类的两个对象可以相等,但很难正确地完成它通常气馁。确保您创建一个{em> Symmetric , Reflexive 和 Transitive 的equals()
方法是非常困难的,但最重要的是你还应该:
hashCode()
equals()
方法
compareTo()
equals()
方法
compareTo()
方法为well behaved 如果不这样做,可能会导致使用集合,树,散列和排序列表中的对象出现问题。
根据Josh Bloch的书 Effective Java 的一部分,equals()
的主题有一个good article,但即使只涵盖equals()
。本书详细介绍了一下,特别是一旦开始使用集合中的对象,问题就会迅速变得严重。
答案 9 :(得分:-1)
正如@andrzeg所说,这取决于你的需要。
另外一件事。你想a.equals(d)是真的,但是d.equals(a)是假的吗?在编码时,您可能会使用instanceof
,这可能是您想要的,也可能不是。
答案 10 :(得分:-2)
我认为这个问题可以简化为是否在equals()实现中使用instanceof或getClass()。
如果D派生A并且您的继承层次结构是正确的那么确实D IS A.将D视为任何其他A是合乎逻辑的,因此您应该能够检查D是否与任何其他A一样相等。
这是一个链接,Josh Bloch解释了为什么他喜欢这种方法的实例。