Java继承的行为不符合预期

时间:2016-04-17 10:44:14

标签: java inheritance

需要以下背景:这种编码方式的目的是避免if - else语句和instanceof;这总是一个坏主意。

我有3个具有以下签名的课程:

abstract class A {}
class B extends A {}
class C extends A {}

然后我有另一个具有以下结构的类:

class MyClass {
    private final A model;

    public MyClass(A m) {
        this.model = m;
    }

    public void doSomething() {
        System.out.println(this.model instanceof C); //TRUE!!
        execute(this.model);
    }

    private void execute(A m) {
        System.out.println("noo");
    }

    private void execute(C m) {
        System.out.println("yay");
    }
}

最后我的主要内容:

public static void main(String... args) {
    C mod = new C();
    MyClass myClass = new MyClass(mod);
    myClass.doSomething();
}

现在问题; execute(C)方法永远不会被执行,它总是执行(A)方法。我怎么解决这个问题?我无法更改执行(A)方法的签名以执行(B),因为这会产生错误,说java"无法解析方法execute(A)"在MyClass#doSomething。

4 个答案:

答案 0 :(得分:5)

方法重载在编译时解决。在编译时,m的类型为A,因此execute(A m)会被执行。

此外,私有方法不可覆盖。

解决方案是使用@OliverCharlesworth建议的访客模式。

答案 1 :(得分:3)

您的代码说明了对象的静态和动态类型之间的区别。静态类型是编译器已知的;动态类型是运行时实际存在的东西。

model字段的静态类型为A

private final A model;

也就是说,编译器知道A本身或其某些的实现将被分配给model。编译器不知道任何其他内容,因此在execute(A m)execute(C m)之间进行选择时,唯一的选择是execute(A m)。该方法是在对象的静态类型上解决的。

另一方面,

instanceof了解动态类型。它可以告诉我model设置为C,因此会在您的打印输出中报告true

您可以通过向A添加方法并在BC覆盖该方法来解决问题,以便路由到正确的execute

abstract class A {
    public abstract void callExecute(MyClass back);
}
class B extends A {
    public void callExecute(MyClass back) {
        back.execute(this);
    }
}
class C extends A {
    public void callExecute(MyClass back) {
        back.execute(this);
    }
}

class MyClass {
    private final A model;

    public MyClass(A m) {
        this.model = m;
    }

    public void doSomething() {
        System.out.println(this.model instanceof C); //TRUE!!
        model.callExecute(this.model);
    }

    public void execute(B m) {
        System.out.println("noo");
    }

    public void execute(C m) {
        System.out.println("yay");
    }
}

请注意,两个实现都会调用

back.execute(this);

但是,B中的实现具有this类型的B,而C内的实现具有类型this的{​​{1}},所以调用被路由到C的{​​{1}}方法的不同重载。

  

我无法将execute方法的签名更改为MyClass

另请注意,现在您也可以(也应该)这样做,因为根据execute(A)的类型对正确的重载执行回调。

答案 2 :(得分:2)

方法重载是编译时多态。因此,对于调用方法execute(C),您需要将模型定义为class C。 最好在execute()中定义方法class A并在子类中覆盖它。

abstract class A {
    abstract void execute();
}
class B extends A {
    public void execute(){};
}
class C extends A {
    public void execute(){};
}

然后:

class MyClass {
    private final A model;

public void doSomething() {
    model.execute();
}

这种更好的方法是使用多态来避免if-else语句和instanceof检查

答案 3 :(得分:-2)

您正在构造函数中发送类型C的对象作为类型A的对象(您已完成向上转换)并将其分配给类型A的引用(这将导致仅调用execute(A)方法)。您可以检查对象是否是C的实例,并根据结果调用所需的方法。你可以这样做

    public void doSomething(){
        System.out.println(model instanceof C);
        if (model instanceof C) execute((C)model);
        else
            execute(model);
    }