简体   繁体   中英

How does java determine, which overloaded method will call, when one method with generic type parameter and other with non generic parameter?

A Java class cannot have two overloaded methods that will have the same signature after type erasure. Java will generate a compile-time error for the below scenario:

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

But my question is not that. There are two scenarios 1 and 2. I want to know, why their behavior is not the same?

Scenario 1 :

import java.util.ArrayList;
import java.util.List;

public class MethodOverload {

    public static void main(String[] args) {
        
        MethodOverload methodOverload = new MethodOverload();

        //Point 1 - List here
        List<Integer> mangoList = new ArrayList<Integer>();
        mangoList.add(1);
        mangoList.add(2);
        
        System.out.println("Size List<Integer>:"+methodOverload.count(mangoList));
        
        //Point 2 - ArrayList here
        ArrayList<String> nameList = new ArrayList<String>();
        nameList.add("Anex");
        nameList.add("Alia");
        nameList.add("Maxim");
        
        System.out.println("Size List<String>:"+methodOverload.count(nameList));
    }
    
    public <T> int count(List<T> list) {
        System.out.print("Ïn side generic; ");
        return list.size();
    }
}

Output, for Scenario 1:

Inside generic block; Size List<Integer> : 2
Inside generic block; Size List<String>  : 3

Scenario 2:

import java.util.ArrayList;
import java.util.List;

public class MethodOverload {

    public static void main(String[] args) {
        
        MethodOverload methodOverload = new MethodOverload();

        //Point 3 - List here
        List<Integer> mangoList = new ArrayList<Integer>();
        mangoList.add(1);
        mangoList.add(2);
        
        System.out.println("Size List<Integer>:"+methodOverload.count(mangoList));
        
        //Point 4 - ArrayList here
        ArrayList<String> nameList = new ArrayList<String>();
        nameList.add("Anex");
        nameList.add("Alia");
        nameList.add("Maxim");
        
        System.out.println("Size List<String>:"+methodOverload.count(nameList));
    }
    
    public <T> int count(List<T> list) {
        System.out.print("Inside generic block; ");
        return list.size();
    }
    
    //Point 5
    public int count(ArrayList<String> list) {
        System.out.print("Inside non-generic block; ");
        return list.size();
    }

}

Output, for scenario 2:

Inside generic block;     Size List<Integer> : 2
Inside non-generic block; Size List<String>  : 3

Now, my question is, how does java decide which method will call in the case of scenario 2 ? In scenario 1 , where java called a single count method without any issue, then why does not java show similar behavior for scenario 2 ?

In Java, method parameter types are part of the method signature

So, the method will be bounded at compile time based on parameter types. (this should not be misunderstood with polymorphic behavior of the reference variable on which the method is invoked, say methodOverload

In the above code,

  1. The first call with reference type as List will be bound to a method that has List as parameter type
  2. Let's assume, there is no method that has List as parameter type and assume there is a method with ArrayList as parameter. How can the compiler guarantee that the List is actually an ArrayList at runtime to maintain type safety promises. Since it can not, it will prohibit such binding.
  3. The second call with type ArrayList will be bound to a method that has exact type ArrayList if present. If such method does not exist, then it will be mapped to List method(with relevant generic type if generics is considered)

Essentially,

  1. The object whose method will be invoked is determined by the polymorphism of the reference variable on which the method is invoked (compiler ensures only the reference variable type class has the relevant method at compile time but does not control a subclass method getting invoked at run time)
  2. Once an object is identified by run time, the method to be invoked on that object is controlled by the compile time binding based on the parameters.

Final thoughts

  1. overriding is a run time behavior determined by object type of the reference on which the method is invoked
  2. overloading is a compile time deterministic behavior for an object chosen by time time based on the parameter types.

Because type erasure only applies to generic types. Java does not "erase" inheritance.

In scenario 1, the count() method signature effectively becomes this at runtime:

public int count(List<Object> list) {...}

so both mangoList (declared a List) and nameList (declared an ArrayList) fit this signature.

In scenario 2, the method signatures effectively become this at runtime:

public int count(List<Object> list) {...}

public int count(ArrayList<Object> list) {...}
  • Since mangoList is declared as a List, Java fits it to the first count() method, taking a List as an argument.
  • Since nameList is declared as an ArrayList, Java fits it to the second count() method, taking an ArrayList as an argument.

Java knows how to do this (inheritance-based) overloading, long before generics came about. What it can't do is overloading based on unbound generic types, because of runtime type erasure, and that's why the compiler stops you doing that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM