6

I'm studying for an exam and I need some help to understand what is going on in the following snipped of code.

class A {
    public void method1(A X) {
        System.out.println("A");
    }
}
class B extends A {
    public void method2() {
        System.out.println("B");
    }
}
class C extends B {
    public void method1(A x) {
        System.out.println("C");
    }
}
class D extends C {
    public void method1(D x) {
        System.out.println("D");
    }
}
public class test {
    public static void main(String[] args) {
        C c = new D();
        B b = c;
        c.method1(c);  // prints C
        b.method1(b);  // prints C
    }
}

Ok this is what I think: c.method1(c) invokes method1 in C and not method1 in D because c is decleard as a C therefore method1 in D is not visible. But b.method1(b) is more difficult. B does not have method1 and I assumed that method1 in the superclass will be used, but it is not. Why is it using the method in C? I put a new D in b but nothing of the specialization of D is visable, because b is from the type B.

Asker
  • 431
  • 5
  • 14
  • 1
    Puzzle of the day ;) – Suresh Atta Sep 28 '15 at 11:20
  • Two statements as an addition to the answers: 1) Overloading is completely resolved at _compile time_ whereas method resolution for overridden methods is a _runtime behavior_. 2) For overriding a method, you must use the same _method signature_. Keep in mind that the return type is not part of the signature (at least in Java). – Seelenvirtuose Sep 28 '15 at 11:26

7 Answers7

5

In summary, here's the visibility of each method at each inheritance level:

class D:

public void method1(D x) { System.out.println("D"); }
public void method1(A x) { System.out.println("C"); }
public void method2()    { System.out.println("B"); }

class C:

public void method1(A x) { System.out.println("C"); }
public void method2()    { System.out.println("B"); }

class B:

public void method1(A x) { System.out.println("A"); }
public void method2()    { System.out.println("B"); }

class A:

public void method1(A x) { System.out.println("A"); }

It's also important to point out that both variables 'c' and 'b' in your example, are the same object, and is a instance of D.

So... If you call c.method1(c);, it prints "C" because D is an instance of A (it's actually a D, but it's also an A by inheritence), so you can call method1(A), which for D, prints "C". (that's a mouth full). Why doesn't it print "D" you'll ask? Because the variable is declared as a C, which the compiler can only link to method1(A).

If you call b.method1(b);, it prints "C" because your variable b is actually an instance of a D, since you created it as new D(). Actually c and b point to the same object of type D.

When a method is called, the JVM looks at the actual type of the object D in this case, and not what it's declared as B.

A good way to remember is when you have something like

B b = new D()

The left part of the equation is mostly used by the compiler. Remember, method1(A) and method1(D) are two different methods (because not the same exact signature, different argument types).

The right part of the equation is used by the JVM at runtime. It defines the actual type, at runtime, of that variable b.

mprivat
  • 21,582
  • 4
  • 54
  • 64
1

The type of the variable referencing the instance is irrelevant to the method invoked.

The type of the object defines which method is invoked; subclasses overriding methods to vary the behaviour of the instance, while still maintaining their assignability to a variable of type of a super class. That is the essence of OO - see Liskov substitution principle.

However, when it comes to overloading methods, the type of varialbe holding the parameter controls which method is invoked - the type of the instance to which it refers is irrelevant.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
1

Lets take your example code and comment which method is called, why it is called and why not everything here is inheriting.

class A {
    public void method1(A X) {
        System.out.println("A");      <--|
    }                                    |
}                                        |
class B extends A {                      |
    public void method2() {              | This is the only overriding happening
        System.out.println("B");         | because they share the signature method1(A)
    }                                    | The other signatures are method1() from class B
}                                        | and method1(D) from class D
class C extends B {                      |
    public void method1(A x) {           |
        System.out.println("C");         |
    }                             -------
}
class D extends C {
    public void method1(D x) {
        System.out.println("D");
    }
}
public class test {
    public static void main(String[] args) {
        C c = new D();
        B b = c;
        c.method1(c);  // prints C
        b.method1(b);  // prints C
    }
}   

Since you are calling the method1 from the Object defined as C you are getting the result C. The only method´s that are known to this class, despite the methods of the Object class, are method1(A) and method1(). Due to your Class beeing defined as C it will correctly call the method from the class C, since the method1 function from class D doesn´t overrite the one from class C.

Edit to answer your comment:

you are invoking the method with the signature method1(A) at this point. Since your Object actually reprent´s the class D it will notice that the method method1(A) is overwritten by the previously inherited class C and therefore will print C. In basic words, the defintion of the class defines which methods you are allowed to call (in the scope of visibility of the object) but the actually type of the object that it is referencing defines how the method is called (if it´s overriten at some point).

SomeJavaGuy
  • 7,307
  • 2
  • 21
  • 33
  • thanks, you made it a bit more clear. But why does b.method(b) prints "C" too? b is form type B and so it only sees method2 in B and method(A X) in A. I think it should call method1 in A? – Asker Sep 28 '15 at 11:23
  • thanks for your Edit. May I use my own words to see if I got it correctly. The only visible methods of b are method1(A x) form A and method2() form B. If I call b.method1(b) the JVM looks at the actual object b (left side) and searches for the most specialized instance of the method1. So it sees method1(A X) form A and form C. Both fit because b a also an A, but it takes method1(A X) form C because it is more specialized. method1(D x) form D is not consider for invocation beause it does not override and is so not visable for b. – Asker Sep 28 '15 at 11:48
1

If I got it right, your question is, why you got printed "C", not "A" for b.method1(b), as you expected. The answer is simple, you are dealing here with a combination of dynamic dispatch (dynamic binding) and static dispatch (static binding). The first is done, based on runtime types, the second one on on compile-time types.

Note, BTW, that in D you have two methods, with different signatures (remember, to override, you need exact match of signatures, so in this case it is rather overloading == compile-time polymorphism):

public void method1(D x)
public void method1(A x)

So, what happens here, when you execute b.method1(b).

You need first to determine the method signature to call and then find the matching method. The first is based on compile-time types. The argument you pass, is of type B (implicit upcast), so it selects method1(A x) (method1(D x) has a signature that does not fit) and will search for method1(A x) upwards the inheritance tree to find the match. The first match is in C. For more information look here: JLS, esp. the explanation:

When a method is invoked (§15.12), the number of actual arguments (and any explicit type arguments) and the compile-time types of the arguments are used, at compile time, to determine the signature of the method that will be invoked (§15.12.2)

The Ancient
  • 382
  • 3
  • 13
  • if method1(D x) in D had been method1(A x), would the jvm have considered its invocation? I think not because b is declared as B and therefore the method is not visible at compile time so it can not be used at run-time or am I worng? – Asker Sep 28 '15 at 13:53
  • Yes, it had been visible, since it had been overriding, not overloading. In that case you had got the output "D". Again, do not confuse static polymorphism, where you select method, based on a signature, and inheritance, where you select the best match from the inheritance hierarchy. `b.method1` call is resolved at runtime, once the correct signature is figured out. And since at runtime b is actually of type D, overridden methods from D will also be looked for. – The Ancient Sep 28 '15 at 14:19
  • And may I encourage you to try to run it with all modifications you can imagine, it may easily prove or disprove you assumptions ;) – The Ancient Sep 28 '15 at 14:28
  • Thank you! But static polymorphism is here important as well. If I had been written, D c = new D(); then it would print "D". Does the JVM consider the dynamic dispatching functionality if the compiler can solve the call destination statically? – Asker Sep 28 '15 at 17:55
0

Your method overriding was broken.

Well the method in Class C is equivalent to method in Class D are different. You are not ovverdien in correctly. The signature is different.

You can test it by writing @Override on top of your method.

    @Override
    public void method1(D x) {
        System.out.println("D");
    }

The above code won't compile and give you an error since you changed the signature of that method in subclass.

Suresh Atta
  • 120,458
  • 37
  • 198
  • 307
0

If we see the code only method method1 in class C is being overridden from B which is eventually inherited from class A. As overriding is runtime polymorphism, though you have created a instance of class D but since these two methods are different its invoking class C method1

If you want to invoke that of D you will need to change the parameter of method to A

Rahul Yadav
  • 1,503
  • 8
  • 11
0

the principal is easy when you call a method of an object. the system is gonna start the research of the method from the class of instantiation of the object, then he is gonna go up to it's direct mother until he found it. for example : if you instantiate with D class, and let's say that the method1 only exist in the A class. so the system is gonna fellow this steps of research :

D(not found) > C(not found) > B(not found) > A(found) 

so he will use the A class method

habibhassani
  • 486
  • 1
  • 6
  • 15