为什么这两个实例不是“equal()”?

3

这很简单,但我显然错过了一些非常重要的东西。

可能是重复问题:
为什么equals方法在两个相同值对象上失败?


Cat cat1 = new Cat("bob");
Cat cat2 = new Cat("bob");
System.out.println(cat1 == cat2); 
//false since these references point to diferent objects
System.out.println(cat1.equals(cat2));
//returns false also??

Cat只是一个简单的类,只有名称。

这里发生了什么,equals()是如何工作的?我以为它会比较对象的所有字段,看来似乎不是这种情况。

我需要为所有的类都覆盖吗?


1
我敢打赌,只有 @john skeet 才能让以上两个陈述都成立。;-) - Mukul Goel
@michael,那只是个玩笑,我认为你应该参考http://meta.stackexchange.com/questions/9134/jon-skeet-facts - Mukul Goel
也许与对象在内存中的引用位置有关... - Kermit
@MichaelKjörling ^ 上面有很多Jon Skeet的趣闻,可以参考链接。祝您愉快 :-) - Mukul Goel
简单来说,Java不支持运算符重载,因此“==”在对象上无法正常工作。 - Atul Agrawal
5个回答

7

是的。

java.lang.Object提供了非常基本的equals()hashCode()实现。特别地,它们不会反射实例类型,这将(1)极其缓慢,并且(2)存在明显的风险,即比较由于各种原因您不想在相等比较中进行比较的字段。

如果您想要equals()hashCode()实际上对比较值相等(而不是引用相等,==执行此操作),则需要在自己的类型中实现两者。

请注意,仅实现equals()是不够的;虽然从技术上讲,这将“起作用”,但它可能导致各种奇怪的问题。简单的经验法则是:既不实现也不只实现一个,而必须同时实现。并且它们必须在相同的字段上工作;如果equals()表示两个实例相等,则在两个实例上调用hashCode()必须返回相同的值(还请参见hashCode() contract)。

通常最好重写toString()方法以提供有意义的对象描述。虽然不是必需的,但只需要在调试器中遇到一次问题就会意识到它的价值。(感谢@JonTaylor提到这个非常有用的相关小贴士。)
而.NET将其称为GetHashCode(),而Java仅使用hashCode()作为函数名称...

3
顺便提一下,我倾向于重写 toString() 方法以便对对象进行有意义的描述。 - Jon Taylor
好观点,@JonTaylor。我也会加上提到那个的。 - user
2
Java无法知道哪些字段对于对象值相等是相关的。有些可能持有内部状态或者与是否应该被视为“相等”的两个实例没有影响的值。此外,反射是昂贵的。 - user
@Luke1111 - 默认的equals是对象标识(而不是字段值相等),其中一个原因是后者实际上非常难以正确处理,并且基本上不可能以一般方式正确处理(如果它是默认行为,则需要)。例如,请参见"如何在Java中编写相等方法"。特别是,当一个类可以有子类时,必须考虑到重要的问题(并基本上按情况处理)。 - Ted Hopp
@MichaelKjörling - 呵呵,是的。不过我在想用户类的子类(例如,“猫”可能有“野猫”,“家猫”,“野生猫”等子类)。 - Ted Hopp
显示剩余3条评论

4

您需要在Cat类中覆盖equals方法。默认的equals方法比较的是对象的引用。

class Cat {
private String name;

public Cat(String name) {
    this.name = name;
}

@Override
public boolean equals(Object obj) {
    if (obj == null)
        return false;
    if (!(obj instanceof Cat))
        return false;
    Cat c = (Cat) obj;
    return this.name == null ? false : this.name.equals(c.name);
}

@Override
public int hashCode() {
    return this.name == null ? 31 : this.name.hashCode();
}

@Override
public String toString() {
    return "Cat Name :" + name;
}
}

参考资料


那很好。但我认为提供不真实的代码会阻碍学习过程。 - Mukul Goel
@MukulGoel 我同意,但有时候最好有完美代码的参考,因为这些方法每次都会被需要。 - Amit Deshpande

0
java.lang.object 提供的 equals() 方法会比较对象的唯一标识符,简单来说,你可以把它看作是内存位置,因此只有当你比较一个对象与其本身(即两个指向同一内存中的对象引用)时才会返回 true。
你需要在 Cat 类中实现自己的 equals() 方法:
class Cat
{  
   String name;

   @Override
   public boolean equals(Cat other)
   {
      if (this.name.equals(other.name))
          return true;
      return false;
   }
}

除非这只是一个非常基本的作业或其他应用,否则覆盖 hashCode() 也是明智的选择。此外,覆盖 toString() 也可能会很有用。

http://docs.oracle.com/javase/tutorial/java/IandI/objectclass.html


或者 return this.name.equals(other.name); - rees
3
你重写的方法应该有一个类型为 Object 的参数,而不是 Cat。你必须使用 instanceof 来检查它是否是 Cat 类型。此外,在访问其参数或方法之前,你需要检查对象是否为空。 - Jon Taylor
这只是一个示例要遵循。他可以执行空值检查的工作 :-) @rees 是的,如果它仅使用名称字符串,他可以返回字符串比较的评估。 - speakingcode
2
如果它接受类型Cat,那么你并没有覆盖,只是创建了一个名为equals的方法,恰好与超类中的一个方法同名。 - Jon Taylor
好的观点,这是重载而不是覆盖。然而,在执行时,如果传递了一个Cat对象作为参数,那么这个方法将被执行...这引发了一些关于Java设计的问题。 - speakingcode
显示剩余2条评论

0

来自[Java Doc]

Object类的equals方法实现了最具有区分性的对象等价关系;也就是说,对于任何非空引用值x和y,当且仅当x和y引用同一个对象时,该方法返回true(x == y的值为true)。

如果不重写equals()方法,则这些对象是不同的。因此

 System.out.println(cat1.equals(cat2)); // is false

0
那是因为==比较引用,而java.lang.Object.equals()转换为this==o,因此在您的情况下返回与==相同。
在上述情况下,您正在使用新操作符创建两个不同的对象,因此都返回false。
如果您希望 .equals()按您的预期工作,则覆盖Cat类中的equals()

实际上,Object.equals(Object o) 只是返回 this == o。没有任何与 == 使用的对象引用不同的“唯一标识符”。你的回答措辞暗示了存在某种差异。 - Ted Hopp
@tedHopp 是的,你说得对,它是以那种方式措辞的。我会纠正并更新它以获得更好的可读性。 - Mukul Goel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接