为什么它似乎调用了错误的方法?

Why does it seem to call the wrong method?

假设我有两个 类 ABB 继承自 A 并且 B 具有以下方法:

public boolean equals(Object other) {
   System.out.print("Object");
   return true;
}
public boolean equals(A other){
   System.out.print("A object");
   return true;
} 
public boolean equals(B other) {
   System.out.print("B object");
   return true;
}
A a1 = new A();
A ab = new B();
B b1 = new B();

我不清楚的是为什么

 ab.equals(a1)
 ab.equals(b1)

Returns Object

abB 的实例,指针为 Aa1 显然是 A 的实例和指针。那么为什么它使用 Object other 而不是 A other 方法呢? b1 也是 B 的一个实例,pointe B 但编译器选择应用 equals 方法,就像我插入常规对象一样?我是不是太笨了还是这语言不连贯?

顺便说一句 A 根本没有任何 equals 方法。

说明

BTW A doesn't have any equals methods at all.

但你有

ab.equals(a1)
ab.equals(b1)

ab是:

A ab = new B();

所以虽然它实际上是一个 B,但变量的类型是 A

在这种情况下选择方法的 Javas 规则将开始在 A class 中寻找 equals 方法。它实际上有一个,从 Object 继承的默认实现。它有签名

public boolean equals(Object other)

所以它决定这个签名。现在它向 ab 后面的实际实例(类型为 B)询问具有该签名的方法。它选择 B:

中的覆盖实现
public boolean equals(Object other) {
   System.out.print("Object");
}

并因此在这两种情况下打印 Object


详情

详细信息在 JLS§15.12. Method Invocation Expressions 中定义。它谈到找到 最具体的匹配项 。如果您深入研究,规则可能会变得相当复杂。

部分摘录:

The first step in processing a method invocation at compile time is to figure out the name of the method to be invoked and which class or interface to search for definitions of methods of that name.

The second step searches the type determined in the previous step for member methods. This step uses the name of the method and the argument expressions to locate methods that are both accessible and applicable, that is, declarations that can be correctly invoked on the given arguments.

There may be more than one such method, in which case the most specific one is chosen. The descriptor (signature plus return type) of the most specific method is the one used at run time to perform the method dispatch.

The class or interface determined by compile-time step 1 (§15.12.1) is searched for all member methods that are potentially applicable to this method invocation; members inherited from superclasses and superinterfaces are included in this search.

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

设置

给定一个 class A 和一个 class B extends A,用不同的参数类型覆盖和重载 equals(...) 如下:

class A {}

class B extends A {
    public boolean equals(Object other) {
        System.out.println("Object");
        return true;
    }
    
    public boolean equals(A other) {
        System.out.println("A object");
        return true;
    } 

    public boolean equals(B other) {
        System.out.println("B object");
        return true;
    }
}

问题

给定以下代码

A a1 = new A();
A ab = new B();
B b1 = new B();

ab.equals(a1);
ab.equals(b1);

为什么两次调用都打印出 "Object"

解释

被调用方法的签名由接收者的静态类型和参数的静态类型决定(如JLS, §15.12.3). Furthermore, through dynamic dispatch中所述,方法的子class覆盖可能是已执行。

如果我们分析ab(调用的接收者)的类型,我们发现它是A。只有一种方法叫做 equals(...) wihtin A,即 equals(Object) (inherites from Object)。参数(分别为 a1b1)可分配给 Object,因此调用此方法。

通过动态调度,B中的equals(Object)被实际执行,并且两次调用都打印出"Object"

Ideone

上提供了完整示例