在Java中,针对超类的对象进行类型转换会返回子类的对象。

3

为了理解静态绑定和动态绑定之间的区别,我已经实现了以下代码:

class A {

    int met(A a) {
        return 0;
    }

    int met(B b) {
        return 1;
    }

    int met(C c) {
        return 2;
    }
}

class B extends A {

    int met(A a) {
        return 3;
    }

    int met(B b) {
        return 4;
    }

    int met(C c) {
        return 5;
    }
}

class C extends B {

    int f() {
        return ((A)this).met((A)this);
    }
}

public class Test {

    public static void main(String[] args) {

        C x = new C();
        System.out.println(x.f());
    }
}

我得到的结果是3,但我不明白为什么,因为第一次转换是转给了A。

一年中没有更好的时间开始学习Java吗? - user3522371
为什么不行呢?你期望发生什么?B 覆盖了那个函数。 - SLaks
1
没有什么时候开始学习Java是不好的:)) - AN00
是的,它会覆盖它,但是转换是针对A而不是B进行的。答案是它在继承线路上自下而上进行吗?换句话说,当我将转换为超类A时,它会取A的最后一个子类? - AN00
2个回答

6
所以,让我们来看看这个调用:
((A)this).met((A)this);

这相当于:

A target = this;
A argument = this;
target.met(argument);

因此,对于最后一行代码,编译器会根据涉及的编译时类型查找“签名”,它将在A(和超类)中查找名为“met”的方法,该方法具有与A(参数的编译时类型)兼容的参数。重载解析发现答案是:
int met(A a)

这决定了在编译时的 签名(signature)。然而,该方法的实现是根据方法调用的执行时目标在执行时确定的。这里的类型是 C - 因为this 是对C实例的引用。(在C实例上调用f方法。)

现在C没有重写int met(A a),但B(它的超类)已经重写了,因此使用的是那个实现。无论B是否还重写了met(B b)met(C c)都无所谓,因为编译器已经确定是调用 met(A a) 方法。


2

这是两个问题的组合。 (1)为什么我们从类B中获取实现,(2)为什么我们获取带有类型A参数的方法版本。

对于问题(1),需要记住的是,当您将对象强制转换或分配给类型不同的变量时,对象的类不会改变。因此,在您的示例中,this的类始终为C,因为这就是您创建的内容。类C从类B继承了其版本的met(A a),因为它没有自己的版本,并且因为类B已经覆盖了类A的版本。这就是多态的全部含义-方法的版本取决于您调用它的对象的,而不是您用于调用它的表达式类型

对于问题(2),Java的一个小怪癖是方法签名在编译时计算。因此,编译器看到您正在将类型A的表达式传递到方法中,因此它选择签名met(A a)。因为这个决定是在编译时做出的,所以参数的实际类别并不重要-编译器已经根据表达式的类型选择了方法。换句话说,Java 不会给您提供方法参数的多态性。


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