2

I know method overloading means defining a method in a subclass with the same name as in parent class but with different parameters and method overriding means defining a method in a subclass with the same signature as in parent class.

However, I feel I miss some understanding. I tried some examples. I defined two classes A and B extends A. Then I prepared many examples with different combinations of two methods (method(A) and method(B)) they can contain. For example:

  • Example 1: A contains both method(A) and method(B) and B contains none
  • Example 2: A contains method(A) and B contains method(B)

and so on. There can be 16 such combinations.

Now I created two instances of class B and called various methods in the first instance. I can go for creating various combinations of instance types, reference types and method calls also but that will be an overwhelming number of combinations to prepare examples.

Doubt is I am struggling to guess what will be the outputs of some examples. Or more specifically I miss understanding or should I say a minimal set of rules which can dictate what will be the output in all examples. The confusion is arising from the fact that reference type is different from the type of instance it is referring to. So I didn't get which method gets precedence, one which is in reference type or one which is in instance type. In the below examples, I didn't get an output of examples 3, 4 and 5. These are only 5 combinations out of possible 16. I believe there might be some more confusing combinations.

Below are some examples

Example 1 – downcasting gives compile time error

class A {
    public void method(B b)
    {
        System.out.println("A's method(B)");
    }
}

class B extends A { 
    public void method(B b)
    {
        System.out.println("B's method(B)");
    }
}
A a = new B();
B b = new B();
a.method(b); //outputs B's method(B)
a.method(a); //compile time error: The method method(B) in the type A 
            //is not applicable for the arguments (A)

Example 2 – upcasting may happen if desired

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }
}

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

Output

B's method(A)  //upcasting happening
B's method(A)

Example 3

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }

    public void method(B b)
    {
        System.out.println("A's method(B)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }
}

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

Output

A's method(B)
B's method(A)

Example 4

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }

    public void method(B b)
    {
        System.out.println("A's method(B)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }

    public void method(B b)
    {
        System.out.println("B's method(B)");
    }
}

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

Output

B's method(B)
B's method(A)

Example 5

class A {
    public void method(A a) 
    {
        System.out.println("A's method(A)");
    }
}

class B extends A {
    public void method(A a) 
    {
        System.out.println("B's method(A)");
    }

    public void method(B b)
    {
        System.out.println("B's method(B)");
    }
}

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

Output

B's method(A)
B's method(A)
MsA
  • 2,599
  • 3
  • 22
  • 47
  • A bit unclear what your question is really! Have you read this https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.8 – Jocke Mar 25 '20 at 12:57

3 Answers3

1

I know method overloading means defining a method in a subclass with the same name as in parent class but with different parameters

Incorrect. A method is overloaded if there is another method with the same name but different signatures in the same class.

Or more specifically I miss understanding or should I say a minimal set of rules which can dictate what will be the output in all examples.

The rules that the compiler and runtime applies when figuring out which method to call is described in section 15.12 of the Java Language Specification. The most relevant subsections to this question are 15.12.1, 15.12.2 and 15.12.4. This answer will essentially be a simplification of what is said there.

There are basically 3 things that the compiler and the runtime needs to decide:

  1. Which class should I search in? (compile time)
  2. Which overload should I call? (compile time)
  3. Which implementation should I call? (runtime)

Step 1 is decided based on the compile time type of the object on which you are calling the method. The class to search is the compile time type of that object.

Step 2 is decided by looking at all the matching overloads found in the class from Step 1, and picking the most specific one.

Step 3 depends on the runtime type.

Let's apply these rules to Example 3, 4 and 5. In all three examples, the class to search is A.

Example 3

Two overloads of method are declared in A. method(A) is overridden in B

  • a.method(b);

    Both method(A) and method(B) are applicable here, but method(B) is more specific, so method(B) is chosen in Step 2.

    method(B) is not overridden in B, so in Step 3 the implementation in A is chosen.

  • a.method(a);

    Only method(A) is applicable so method(A) is chosen at Step 2, because the compiler doesn't know the runtime type of a. Note that Step 2 is carried out at compile time.

    method(A) is overridden in B, so the implementation in B is chosen in Step 3.

Example 4

Same as Example 3, except that method(B) is also overridden in B, so a.method(b); calls the implementation in B.

Example 5

Only one overload of method is declared in A. B declares 2 overloads. One of them overrides method(A) in A.

Unlike the other examples, in resolving a.method(b);, the compiler can't find a method(B) in A anymore, so the best applicable overload is method(A) in Step 2. And then the implementation in B is chosen in Step 3.

Resolving a.method(b); is similar to Example 4.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I tried to help my self with some analysis as explained in [this image](https://i.postimg.cc/Pr105XR1/image.png). Can you please tell if I am correct? – MsA Mar 25 '20 at 16:45
  • @anir Wow! That's a great table! If only I can do that with the Stack Overflow editor... Your wording in the first part is a bit non-standard in the first part, but I think you do understand the concepts. [I've edited your image to correct some relatively more significant mistakes](https://imgur.com/H97Hbxz). I definitely suggest you to try to read the relevant sections in the language specifications for the official terminology, even though it is quite hard to read. The truth is, if you ignore all the bits irrelevant to the question (like variable arity and stuff like that), there's not a lot – Sweeper Mar 25 '20 at 18:19
  • Not able to get "if you ignore all the bits irrelevant to the question (like variable arity and stuff like that), there's not a lot". Do you mean to say that my question contains unnecessary things that can be further reduced while still covering everything or those steps in my pic contains something unnecessary? – MsA Mar 26 '20 at 05:24
  • @anir I mean the *language spec* contains lots of things that are irrelevant to the context of this question, so you can ignore those parts when you are reading through it. – Sweeper Mar 26 '20 at 07:19
1

The decision of which method is invoked is done in three steps.

  • Step 1: (At compile time) Find reference type at compile time.
  • Step 2: (At compile time) Find most specific method in the reference type that matches call. Here, three points need to be considered:
    • Meaning of “most specific”: If B extends A, A contains method(A) and method(B) and call is A.method(B), then A.method(B) is more specific than A.method(A) for this call.
    • Down casting is not allowed: If A contains only method(B) and B extends A, then call to A.method(A) will lead to compile time error as we cannot down cast A to B.
    • Up casting is allowed: If A contains only method(B), and B extends A, then call to A.method(B) will first up cast B to A and then map method(B) to method(A).
  • Step 3: (At run time) Now select implementation in the lowest class from the chain reference type to instance type. If class C extends B extends A and matched method has implementation in class B, then execute that method.

I prepared following table to help myself analyse how the calls in the examples I listed in the question are done.

enter image description here

Sorry for non markdown content. But I feel I cannot prepare equivalent table in markdown, so I prepared one in Word and shared its pic here. I hope it will help everyone struggling like me for understanding topic which is usually considered basics of Java OOP but still considerably complex.

MsA
  • 2,599
  • 3
  • 22
  • 47
-1

Java always attempts to pick the most specifc method when overloading. So it first tries to resolve the method from the variable type (A or B) and then calls the method on the instance. That's why in the third example it calls the method B.method(A).

You see the difference, the method is resolved via the declared type of the variable, but the method is called on the instance of the variable. So even when your variable a is in reality an instance of B it still resolves the method via the statically known methods of class A.

This gets even more clear in your fifth and last example. Your class A only defines a single method which has as an argument any instance of A. It doesn't care if any subclass has a method with a more specific argument e.g. method(B) it still calls the method(A), because that is the only known method of the variable a of type A

Lino
  • 19,604
  • 6
  • 47
  • 65