3

I have a class named A, and a class named B that extends A. Playing with some methods to understand polymorphic behaviour, I ran into a weird situation.

public class Main {
    public static void main(String[] args){
        B b = new B();
        A a = b;
        b.f1(a);
    }
}

public class A {
.
.
.
    public void f1(A a){
        if(a instanceof B)
            f1((B)a);
        else
            System.out.println("Nothing");
    }
.
.
.
}

public class B extends A {
.
.
.
    public void f1(B b){
        System.out.println("B::f1(B)");
    }
.
.
.
}

I expected f1 in class A to be called first(because a is of type A) which actually happened. Then I expected the line f1((B)a); to be called, since a is an instance of B. Until now everything went as expected. However, I thought that the next method that will be called is f1(B) in class B. Instead, f1(A) in class A was called over and over causing a stack overflow exception. Why wasn't the f1(B) in class B called? An instance of B was the caller and the parameter was cast to type B.

  • 1
    It is because f1() is an instance method, B's instance knows its instance method, not A's instance method. Same is true in reverse also – Hari Krishna Feb 06 '19 at 11:10
  • [Which overloaded method is called in java?](https://stackoverflow.com/questions/50014088/which-overloaded-method-is-called-in-java) – ernest_k Feb 06 '19 at 11:16

3 Answers3

4

f1(A a) is an instance method of class A. It has no knowledge of methods of sub-classes of A. Therefore, it cannot call void f1(B b) of class B. Therefore, f1((B)a) executes void f1(A a) again.

If you want to call f1(B b), you'll have to call f1 on an instance variable of class B:

public void f1(A a){
    if(a instanceof B) {
        B b = (B)a;
        b.f1(b);
    } else {
        System.out.println("Nothing");
    }
}
Eran
  • 387,369
  • 54
  • 702
  • 768
  • Oh, I get it now. What about the opposite way, can I call a method written in class A from a method written in class B(assuming the method is not overridden in class B of course)? – Ronen Hanukayev Feb 06 '19 at 11:16
  • 1
    @RonenHanukayev yes, since B can see all the (public or protected) methods of its super-class A. – Eran Feb 06 '19 at 11:18
  • @Eran please note my answer. Doing that can generate a ClassCastException so the questionnaire should be advised. – Davide Lorenzo MARINO Feb 06 '19 at 11:31
  • 1
    @DavideLorenzoMARINO How can a ClassCastException be thrown if you check that `a instanceof B` before doing any cast? – Eran Feb 06 '19 at 11:32
  • Oh I see... you didn't casted "this", but you called f1 on the instance of the parameter. Interesting trick. +1 – Davide Lorenzo MARINO Feb 06 '19 at 11:34
  • @Eran good trick, but in original question f1(B) should be called on `this` object, not on argument, so `((B) this).f1(b);` is what probably expected. – Serge Ageyev Feb 06 '19 at 13:14
1

Your class A has no any idea that class B exists somewhere and has B.f1(B b) function. Actually f1(A a) and f1(B b) are two different functions. what you want probably to achieve, should be done in bit different way:

public class A {
//...
    public void f1(A a) {
         System.out.println("Nothing");
    }
//...
}

public class B {
//...
    public void f1(B a) {
        // this is different function, because of another parameters
    }

    public void f1(A a) {
        if(a instanceof B)
            f1((B)a);
        else
            super.f1(a);
    }
//...
}
Serge Ageyev
  • 437
  • 3
  • 8
1

The code must cast the caller of the method as follow:

public class A {

    public void f1(A a){
        if(a instanceof B)
            ((B) this).f1(a);
        else
            System.out.println("Nothing");
    }
}

In your code instead you are casting only the parameter. Doing that result in a call on the same method of the same class recursively.

If you cast the caller instead you inform the JVM that you like to call the method on the subclass, so you can exit from the method immediately.

Important Note that invoking a cast on the caller depending on the parameter can generate a class cast exception, for example in the following context:

    B b = new B();
    A a = new A();
    a.f1(b);

You can't be sure that the method f1 receiving a B parameter is called on B object.

Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • Probably `((B) this).f1((B)a);` is what author need, since `f1(B b)` is what expected to be called for argument of type `B`. I think that it would be great to check that `this` actually can be casted to `B` as well :) – Serge Ageyev Feb 06 '19 at 14:19