为什么 Object.equals 可以比较无法与引用相等运算符 (==) 比较的类型?

Why can Object.equals compare types that cannot be compared with the reference equality operator (==)?

提问人:saga 提问时间:9/19/2021 最后编辑:M. Justinsaga 更新时间:11/28/2021 访问量:1120

问:

我知道在 Java 中您不能使用 比较两个不相关的实例,因为它会产生编译错误(不兼容的类型)。例如==

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

Dog d = new Dog();
Cat c = new Cat();
System.out.println( d == c );

是编译时错误。

但是,为什么使用继承自的等号不会产生这种错误:Object

System.out.println( d.equals(c) );  // is false

即使内部使用?Object.equals==

现在,我知道为什么会这样,但我不知道为什么它不是错误,因为签名是:false

public boolean equals(Object obj) {
    return (this == obj);
}
Java 对象 等于 相等

评论

2赞 Gaël J 9/19/2021
因为在你比较的类型中都是?Object#equalsObject
0赞 saga 9/19/2021
@GaëlJ:我觉得你说得没错。我测试了: ' 对象 od = d;对象 oc = c;System.out.println( od == oc );' 这是错误的,不是编译错误,所以你的答案是正确的。
0赞 VGR 9/19/2021
编译器不必检查任何代码即可知道 Dog 的实例不能是 Cat 的实例,因为这两个类都不是从另一个类继承的。但是,若要确定调用是否保证为 false,编译器必须检查方法的逻辑。从理论上讲,如果这两种类型都继承自 java.lang.Object,则可以提供相同的编译错误,但这种特殊情况可能不值得在编译器中实现。equalsequalsequals

答:

2赞 Gaël J 9/19/2021 #1

因为在你比较的类型中都是 ,因此运算符是“允许的”。Object#equalsObject==

但请记住,大多数时候您不想使用 == 或默认的 Object#equals 进行比较,因为它比较的是引用,而不是内容。见 https://stackoverflow.com/questions/7520432/what-is-the-difference-between-and-equals-in-java?r=SearchResults&s=3|156.7237

0赞 M. Justin 11/28/2021 #2

编译时检查在引用相等的站点(在 中)执行。在您引用的方法的上下文中,它与:Object.equalsObject.equalsthisObject

public boolean equals(Object obj) {
    return (this == obj);
}

根据 Java 语言规范

如果无法通过强制转换 (§5.5) 将任一操作数的类型转换为另一个操作数的类型,则为编译时错误。两个操作数的运行时值必然不相等(忽略两个值均为 的情况)。null

由于可以转换为 ,是引用相等运算符 () 的有效用法,因此它可以编译。thisObjectthis == obj==


举一个实际的例子,观察 Java API 中的一种情况,即两个对象不能通过引用相等 () 进行比较,但在通过 进行比较时相等。==Object.equals

首先,注意 的实现只是 的基础实现。如果适用,鼓励子类使用不同的实现。Object.equalsequalsObject

其次,请注意,与引用等于运算符 () 不同,equals 方法对两个比较对象是否可以转换为另一个对象的类型没有限制。根据 的 Javadocs,这两个对象只需要实现一个等价关系==Object.equals

该方法在非 null 对象引用上实现等价关系:equals

  • 它是自反的:对于任何非空引用值,应返回 。xx.equals(x)true
  • 它是对称的:对于任何非空引用值和 ,应返回当且仅当返回 。xyx.equals(y)truey.equals(x)true
  • 它是可传递的:对于任何非空引用值,和 ,如果返回 和 返回 ,则应返回 。xyzx.equals(y)truey.equals(z)truex.equals(z)true
  • 它是一致的:对于任何非空引用值和 ,只要没有修改对象比较中使用的信息,就会多次调用一致返回或一致返回。xyx.equals(y)truefalseequals
  • 对于任何非空引用值,应返回 。xx.equals(null)false

以 Java List 接口为例。它指定实现类必须如何实现 equals:

将指定的对象与此列表进行比较,以确保相等。当且仅当指定的对象也是列表,两个列表具有相同的大小,并且两个列表中所有对应的元素对都相等时,才返回。(两个元素相,如果。换句话说,如果两个列表以相同的顺序包含相同的元素,则它们被定义为相等。此定义可确保 equals 方法在接口的不同实现中正常工作。truee1e2Objects.equals(e1, e2)List

现在,获取两个无法相互强制转换的列表,例如 ArrayListLinkedList。不能使用引用相等误差 () 将引用与引用进行比较,因为这两种类型都不能强制转换为另一种类型。另一方面,如果它们以相同的顺序包含相同的元素,则 an 将是 a。LinkedListArrayList==ArrayList.equalsLinkedList

ArrayList<String> a = new ArrayList<String>(Arrays.asList("X", "Y", "Z"));
LinkedList<String> b = new LinkedList<String>(Arrays.asList("X", "Y", "Z"));

assert a.equals(b); // true
assert a == b; // compiler error