1

When I was analyzing a simple java code related with overloading and inheritance I expected to recieve an output that overloads matching the argument's data types. But it doesn't work that way.

Code:

class A {
        public int calc (double num){
            System.out.println("calc A");
            return (int)(num+1);}
    }
class B extends A{
        public int calc (long num){
            System.out.println("calc B");
            return (int)(num+2);}
    }
class C extends B{
        public int calc (int num){
            System.out.println("calc C");
            return num+3;}
    }
class D extends C{
        public int calc (float num){
            System.out.println("calc D");
            return (int)(num+4);}
    }

class Program{
        public static void main(String[] args){
            int num1=10;
            long num2 = num1;

            Object o1 = num1;
            System.out.println("num1 Type: "+o1.getClass().getName());

            Object o2 = num2;
            System.out.println("num2 Type: "+o2.getClass().getName());

            A a1=new D();
            A a2=new D();

            System.out.println("a1 Type: "+a1.getClass().getName());
            System.out.println("a2 Type: "+a2.getClass().getName());

            int result = a1.calc(num1)+a2.calc(num2);
            System.out.println("Number: "+result);
        }
    }

Output:

num1 Type: java.lang.Integer
num2 Type: java.lang.Long
a1 Type: D
a2 Type: D
calc A
calc A
Number: 22

I was testing the code here: ideone

2 Answers 2

2

Your main question seems to be about why the type outputs don't match with the formal types. This is entirely intentional, and it's what makes object oriented programming so powerful.

When a method is invoked on an instance, the runtime system looks at the actual type of the instance, and looks up the method to call based on its actual type, rather than on its formal type.

If this weren't the case, you wouldn't be able to get anything useful done. You want to be able to declare an abstract class A, with concrete classes B and C hanging off it that implement the details in different ways. But you also want to be able to declare variables of type A, without caring where they've come from, and whether they're actually of type B or type C. You can then invoke methods that are part of the contract of A, and it'll do the right thing: something that's really a B will invoke B's implementation, and likewise for C.

As for why you end up invoking A's calc method rather than D's, this is again because of the way polymorphism works. The formal type of the variables is A; so when you invoke .calc(), the type system will:

  1. find the most appropriate method in class A to match the call at compile time;
  2. see whether that has been overridden between A and the actual type at runtime;
  3. call the overridden version if there is one, or A's version if not.

But you haven't overridden the calc() method at all: you've supplied methods with different signatures. So in step 1 (at compile time) the type system finds A.calc(double); in step 2 (at runtime) it discovers that this hasn't been overridden further down the class hierarchy; in step 3 (runtime) it therefore invokes A's version.

Overloads are resolved at compile time based on formal types; overrides are resolved at runtime based on actual types.

Sign up to request clarification or add additional context in comments.

3 Comments

I thought that invoking an overloaded method like this, first the actual class method (in this example D) will be called (not the one of the variable -> A), and if not matches the contract goes to the parent class and so on... Works that way in this piece of code: ideone.com/lNZlmH
@carvallo in the code you've linked to, that's an override, not an overload. An override has the same name and same parameter list; an overload has the same name but a different parameter list. Really an overloaded method is a totally different method.
OK, I understand that the second example is an override... so, only to check that I understood well: In an override the VM chooses the method (at runtime) matching the contract of the object (not of the variable), and in an overload the compiler chooses the method according to the type of the variable that calls it. If it works that way, the confusing thing for me is that in my example when I check the type of the objects in both cases it returns D, but both of the variables that stores them are of the type A, and thats why the method that is called is from the A class.
0

This is because those methods are overloads, not overrides of the original calc method. Therefore, if you are using a reference of type A, all that can be seen are methods that originally belonged to A. All the other methods are hidden in the object, just as if you had written them with new names.

So when the compiler has to decide which method to call for each calculation, it doesn't have all the options that you think it has. It just has the original calc(double), so it compiles the call as "convert the value to double and call calc(double)". At compile time, it doesn't know that the actual class is not A. It can't compile into code that says "check at runtime if there is a method called calc(int), if so, use it, if not, convert to double and use calc(double). It needs to know what instructions to put there at compile time. And at that time, all it knows about this reference is that it's an A.

EDIT in response to comments:

The compiler always chooses which method is going to be invoked using the contract of the reference's type. That is, the type of your variable, which is A in this case.

This happens whether or not the actual object has an overriding method. At this point the compiler doesn't know about it. What it does is tell the runtime environment: "When you get to this point, take the actual object, and run the method with this signature: calc(double)".

So, if at runtime, the actual object has also calc(int) and calc(long) and other methods named calc, it doesn't matter, because the compiler said "use calc(double)".

Now, if the runtime object has an overriding calc(double), the runtime environment will take that instead of the original calc(double) because that's the nature of overriding.

To sum up:

  1. The compiler only knows about method signatures that exist in the reference type - your variable, in this case.
  2. The compiler puts instructions that mean "use the method with this specific signature or any override (with the same signature).
  3. The runtime environment looks at the actual object, and checks what kind of calc(double) it has. If it has an override, it will use that. If it has only the original, it will use that.

5 Comments

I thought that invoking an overloaded method like this, first the actual class method (in this example D) will be called (not the one of the variable -> A), and if not matches the contract goes to the parent class and so on... Works that way in this piece of code: ideone.com/lNZlmH
@carvallo The code in your ideone example includes an overridden method, not an overloaded one. The compiler tells it to call the method from the parent, but at runtime, the actual object has another method replacing it so that one is used. It doesn't know at compile time that there will be a replacement.
OK, I understand that the second example is an override... so, only to check that I understood well: In an override the VM chooses the method (at runtime) matching the contract of the object (not of the variable), and in an overload the compiler chooses the method according to the type of the variable that calls it. If it works that way, the confusing thing for me is that in my example when I check the type of the objects in both cases it returns D, but both of the variables that stores them are of the type A, and thats why the method that is called is from the A class.
@carvallo I added some information to my answer in an attempt to clarify this. The point is that the compiler chooses neither an overload nor an override. It only looks at the strict definitions in the varible class.
Thanks a lot @RealSkeptic !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.