2

I totally understand the erasure of generic and therefore java cant do overloading only base on generic. But what if we take lambda expression into consideration?

See the following case.

interface base{
    void test();
}
// compiler cant distinguish these two interfaces by lambda expression we provided 
interface Foo extends base{}
interface Boo extends base{}

public static void main(String[] args) throws ClassNotFoundException {
    HashMap<String,Long> set = new HashMap<>();
    // this is valid, since type Foo is explicitly given out
    t1(set,new Foo(){
        @Override
        public void test(){
        }
    }); // "Long"

    // using lambda expression make the second argument indistinguishable, 
    //so the overloading can only depend on the first argument which is a
    //Hashmap with generic type.
    // but the compiler still managed to work this out
    t1(set,()->{});// "Long"

    HashMap<String,Integer> set2 = new HashMap<>();
    t1(set2,()->{});// "Integer"
}
public static void t1(HashMap<String,Long> set,Foo foo){System.out.println("Long");}
public static void t1(HashMap<String,Integer> set,Boo boo){System.out.println("Integer");}

The result of each function call is exactly what I expected without any compile or runtime error. And that's the weired part of the whole thing: suddenly the function is overloading based on generic types.

So what is actually going on behind the scenes?

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
Custer
  • 21
  • 3
  • I don't understand your question. You're passing a `HashMap`. Why would there be any confusion with the other method? – Sotirios Delimanolis Jul 21 '16 at 17:36
  • This basically duplicates https://stackoverflow.com/questions/21905169/java8-ambiguity-with-lambdas-and-overloaded-methods?rq=1. See the accepted answer. – JudgingNotJudging Jul 21 '16 at 17:48
  • @SotiriosDelimanolis Because java wont overload on generic type. HashMap and HashMap is same after compilation. – Custer Jul 21 '16 at 20:41
  • @JudgingNotJudging I have already checked that question before posting this one. The difference is that, in my case, compiler dont know which interface to use, so it is somehow forced to check the first argument which is a hashmap with generic type. It's not about lambda, it's about overloading with generic type. – Custer Jul 21 '16 at 20:49
  • In your comment you're talking about a single parameter. That's not what you have in your question. The compiler has to unambiguously choose an applicable method. The only method that is applicable when you invoke with `t` with two arguments where the first is a `HashMap` is the one where the second parameter is a `Foo`, but not because it's a `Foo`, but because the first parameter is a `HashMap`. – Sotirios Delimanolis Jul 21 '16 at 20:52
  • @SotiriosDelimanolis If I do `void t1(HashMap map,int i)` and `void t1(HashMap map,int i)`. It is not allowed by the compiler and raise an error with an error message: both methods has same erasure. – Custer Jul 21 '16 at 21:03
  • Yes, that's the point. They cannot have the same erasure. `t1(HashMap set,Foo foo)` and `t1(HashMap set,Bar bar)` don't have the same erasure. The fact that you can invoke them with an expressions that look the same `t1(setX, ()->{});` is just a result of target typing. A lambda expression is resolved based on its target type in an assignment expression, invocation expression (as an argument), or casting expression. – Sotirios Delimanolis Jul 21 '16 at 21:08
  • @SotiriosDelimanolis Alright, may be I should rephrase my question: Is this the only known way to force compiler do overload based on generic type? I dont really care how the lambda is resolved. What I care about is java dont allow overloading with generic type but in this case it just did. – Custer Jul 21 '16 at 21:41
  • Create subtypes of `HashMap` and `HashMap` and use those subtypes as the parameter types. Or don't use overloading and name your method appropriately. – Sotirios Delimanolis Jul 21 '16 at 21:55
  • @SotiriosDelimanolis Get it! Thanks – Custer Jul 21 '16 at 22:30

1 Answers1

0

Interesting question.

The compiler decides at compile time what type the lambda function is, so even though the generics are wiped away at that point, the type of the function is also set at that point, meaning it knows what method has to be called.

My guess is it can do that because it still has the generic information at that stage -- i.e. it is the compiler that then gets rid of it.

If you look at the bytecode for the class it says:

16: invokestatic  #25                 // Method t1:
     (Ljava/util/HashMap;LTest$Foo;)V
25: invokestatic  #25  // Method t1:   
    (Ljava/util/HashMap;LTest$Foo;)V

So for the HashMap the typing is gone, but for the interface, the type has been set.

gus
  • 961
  • 6
  • 4
  • So it's true that we can use this trick to force compiler do overloading based on generic type? – Custer Jul 21 '16 at 20:58
  • I guess so, but if you have to have a different interface representing each possible variation, it somewhat negates any advantage you might gain from it. – gus Jul 22 '16 at 15:10