Pieter De Clercq
Pieter De Clercq

Reputation: 1961

Java inheritance not behaving as expected

The following context is needed: The purpose of this way of coding is to avoid if-else statements and instanceof; which is always a bad idea.

I have 3 classes with the following signatures:

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

Then I have another class with the following structure:

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");
    }
}

And finally the contents of my main:

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

Now the problem; the execute(C) method never gets executed, it's always the execute(A) method. How can I solve this? I cannot change the signature of the execute(A) method to execute(B) since that would give an error saying java "cannot resolve method execute(A)" at MyClass#doSomething.

Upvotes: 1

Views: 113

Answers (4)

Andrija Boricic
Andrija Boricic

Reputation: 1

You are sending object of type C as an object of type A in constructor( you've done upcasting) and assigning it to a reference to type A(which will result in calling only execute(A) method).You could check if the object is a instance of C and depending on the outcome, call the desired method. You could do it like this

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

Upvotes: -2

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726479

Your code illustrates the difference between a static and a dynamic type of an object. Static type is what's known to the compiler; dynamic type is what's actually there at runtime.

The static type of your model field is A:

private final A model;

That is, the compiler knows that A itself or some of its implementations is going to be assigned to model. The compiler does not know anything else, so when it comes to choosing between execute(A m) and execute(C m) its only choice is execute(A m). The method is resolved on the static type of the object.

instanceof, on the other hand, understands the dynamic type. It can tell that the model is set to C, hence reporting the true in your printout.

You can solve it by adding a method to A and overriding it in B and C to route to the proper 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");
    }
}

Note that both implementations call

back.execute(this);

However, the implementation inside B has this of type B, and the implementation inside C has this of type C, so the calls are routed to different overloads of the execute method of MyClass.

I cannot change the signature of the execute(A) method to execute(B)

Also note that now you can (and should) do that, too, because callbacks are performed to the correct overload based on type of this.

Upvotes: 3

user207421
user207421

Reputation: 310850

Method overloads are resolved at compile time. At compile time, the type of m is A, so execute(A m) gets executed.

In addition, private methods are not overridable.

The solution is to use the Visitor pattern as suggested by @OliverCharlesworth.

Upvotes: 5

Sergii Bishyr
Sergii Bishyr

Reputation: 8641

Method overloading is a compile time polymorphism. Thus, for calling method execute(C) you need to define your model as class C. It's better to define method execute() in class A and override it in subclasses.

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

And then:

class MyClass {
    private final A model;

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

This much better way to use polymorphism to avoid if-else statements and instanceof checking

Upvotes: 2

Related Questions