Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method overriding and overloading in Java

I was testing a code when I faced something strange that I could not figure out why this is happening. So I will give a simple example of what happened there.

Consider these classes

public class A {
    public void print(A a) {
        System.out.println("A");
    }
}

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

public class C extends B {
    public void print(A a) {
        System.out.println("C");
    }
}

public class E extends C {
    public void print(E e) {
        System.out.println("E");
    }
}

Now in my main method I have this kind of instantiation:

    B b = new E();
    C c = new E();
    E e = new E();

I call these methods and get the following output.

    b.print(b);
    c.print(e);

OUTPUT:

C
C

I have two explanations but each of them is contradictory to one of these method calls. Also it is possible that both of my explanations are totally wrong so I am not claiming anything here.

Explanation 1

  • b.print(b): b is an instance of E and is upcasted to B. B have a print method that takes an argument of type A but it is overloaded by class E. However the argument to this method is b which is upcasted to B, and therefore the method call matches the signature of C.print(A a) (since C is a superclass of E) and therefore the result is rational.

  • c.print(e): Thinking the same way as above is not going to explain the output here. c is an instance of E and is upcasted to C. C have a print method that takes an argument of type A but it is overloaded by class E. But in contrast to the above case, the argument to this method is e which matches the signature of E.print(E e). So By this reasoning the output should have been E which is not!

Explanation 2

here I start with the second method call and reason about that.

  • c.print(e): c is an instance of E and is upcasted to C. C have a print method that takes an argument of type A. The argument to this method is e which is an instance of E which in turn is a subclass of A. Since it has been upcasted E.print(E e) is hidden from c. Therefore, the method call matches the signature of C.print(A a) and the output is rational by this logic.

  • b.print(b): b is an instance of E and is upcasted to B. B have a print method that takes an argument of type A (again it is hidden). Hence, the method call matches the signature of A.print(A a) and by this logic the output should have been A which is not.

I am really really confused about what is going on here. Could someone please explain.

like image 313
Reza Avatar asked May 24 '13 22:05

Reza


4 Answers

Which method will be called is decided in two steps:

  • At compile time it is decided which of the overloaded methods will be used based on the the declared instance type
  • At runtime it is decided which of the overridden methods will be used based on the actual instance type (polymorphism)

    1. b.print(b); // B b = new E();

      • At compile time, since the declared type of b is B, print accepting an instance of B (or its superclasses (A)) can only be used, which means: A.print(A a)

      • At runtime, once the overloaded methods has been selected in the previous step, the actual type of b (E) is used to select the version of print(A a) that will be used: C.print(A a) over A.print(A a)

    2. c.print(e); // C c = new E(); E e = new E();

      • At compile time, the declared type of c is C and the declared type of e is E so only these methods can be used: A.print(A a) and C.print(A a)

      • At runtime, the actual type of e is E therefore the more specific (meaning higher in the class hierarchy) version is selected: C.print(A a)

like image 98
Adam Siemion Avatar answered Nov 17 '22 23:11

Adam Siemion


I don't mean to pile on to tieTYT's answer, but I thought it might be instructive to look at the bytecode for your main method. I wrapped your code in an outer class called Foo and used javap -classpath . -c -s Foo to disassemble it. Here's what we get:

public static void main(java.lang.String[]);
  Signature: ([Ljava/lang/String;)V
  Code:
     0: new           #2                  // class Foo$E
     3: dup           
     4: invokespecial #3                  // Method Foo$E."<init>":()V
     7: astore_1      
     8: new           #2                  // class Foo$E
    11: dup           
    12: invokespecial #3                  // Method Foo$E."<init>":()V
    15: astore_2      
    16: new           #2                  // class Foo$E
    19: dup           
    20: invokespecial #3                  // Method Foo$E."<init>":()V
    23: astore_3      
    24: aload_1       
    25: aload_1       
    26: invokevirtual #4                  // Method Foo$B.print:(LFoo$A;)V
    29: aload_2       
    30: aload_3       
    31: invokevirtual #5                  // Method Foo$C.print:(LFoo$A;)V
    34: return        

The interesting lines are 26 and 31. Notice that on both lines the compiler selected methods that take arguments of type A, but that seems to violate our intuition. We expected E.print(E) on line 31, but we didn't get it.

This happens because the Java compiler doesn't know the actual types of the b, c, and e variables at compile time; it only knows their declared types. In this case you're using a class constructor to create the objects, but imagine if you used static factory methods instead. The types of those variables could vary based on very complex logic, possibly involving reflection. The compiler may be able to determine their actual types in some cases, but it can't possibly do it in all cases. As a result, the compiler has to decide which method to call based on the declared type of the variables rather than their actual type.

You might be wondering why line 26 prints "C" even though the bytecode says to call B.print(A)... and wait a minute, B doesn't even declare a print(A) method; it inherits print(A) from A. So why doesn't the bytecode for line 26 say // Method Foo$A.print:(LFoo$A;)V?

That's where method overriding comes in. At runtime, the Java interpreter will use the actual type of the object to determine which version of print(A) gets called. Since both objects are of type E, and E doesn't have a print(A) method of its own, Java winds up calling C.print(A).

like image 36
debaser Avatar answered Nov 18 '22 00:11

debaser


I've modified your sample code to look like this:

package com.sandbox;

public class Sandbox {
    public static void main(String[] args) {
        B b = new E();
        C c = new E();
        E e = new E();
        b.print(b); //C
        c.print(e); //C
        e.print(e); //E
        e.print(b); //C
    }


    public static class A {
        public void print(A a) {
            System.out.println("A");
        }
    }

    public static class B extends A {
        public void print() {   //doesn't override or overload anyone
            System.out.println("B");
        }
    }

    public static class C extends B {
        public void print(A a) {    //overrides "A"
            System.out.println("C");
        }
    }

    public static class E extends C {
        public void print(E e) {    //Overloads A's print
            System.out.println("E");
        }
    }
}

Since E's method is just overloaded, you can rename it like this:

package com.sandbox;

public class Sandbox {
    public static void main(String[] args) {
        B b = new E();
        C c = new E();
        E e = new E();
        b.print(b); //C
        c.print(e); //C
        e.unrelatedMethod(e); //E
        e.print(b); //C
    }


    public static class A {
        public void print(A a) {
            System.out.println("A");
        }
    }

    public static class B extends A {
        public void print() {   //doesn't override or overload anyone
            System.out.println("B");
        }
    }

    public static class C extends B {
        public void print(A a) {    //overrides "A"
            System.out.println("C");
        }
    }

    public static class E extends C {
        public void unrelatedMethod(E e) {
            System.out.println("E");
        }
    }
}

And things start to make more sense. I think what's really confusing about your sample is that your methods have the same name, but they aren't really the same in the same way.

Let me know if this makes it clear. Those two samples are exactly the same, the only difference is the naming is more clear.

like image 3
Daniel Kaplan Avatar answered Nov 17 '22 23:11

Daniel Kaplan


This is the relevant documentation, but the short story is:

  • the process of choosing the right method always consists of both the static (overriding) and the dynamic (overloading) part; it might be a bit more intuitive if it stopped after overriding;
  • so in your case, first we have the static resolution (choosing the overriden method of class A) and then the dynamic part, at runtime, chooses the best overload of this method (class E).
like image 1
fdreger Avatar answered Nov 18 '22 00:11

fdreger