Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous reference with generic types when using JDK 1.8

Tags:

java

generics

I know that variants of this question have been asked before, and I thought I understood the Java 8 type resolution system, but I am getting an ambiguous reference error for something that I really don't think should be ambiguous:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

interface Function <T, E> {}

public class MyFns {
  public static <E, T> Collection<E> map(Function<? super T, E> fn, Collection<T> coll) {
    return new ArrayList<>();
  }

  public static <E, T> List<E> map(Function<? super T, ? extends E> fn, List<T> coll) {
    return new ArrayList<>();
  }

  public static <E, T> List<E> map(Function<? super T, ? extends E> fn, T[] coll) {
    return map(fn, Arrays.asList(coll));
  }
}

The third overload generates two compile errors when compiled through JDK 1.8:

Error:(18, 12) java: reference to map is ambiguous
  both method <E,T>map(Function<? super T,E>,java.util.Collection<T>) in MyFns and method <E,T>map(Function<? super T,? extends E>,java.util.List<T>) in MyFns match

and

Error:(18, 15) java: incompatible types: java.util.Collection<capture#1 of ? extends E> cannot be converted to java.util.List<E>

Now besides the fact that the return type here could be used to resolve the ambiguity and avoid both errors, even without the compiler doing that, why is the call itself ambiguous? Function<? super T, ? extends E> should not be castable to Function <? super T, E>, because the latter has a more specific type that doesn't include a wildcard. Second, even if it were, shouldn't the compiler choose the more specific overload, which in this case would be to take the second argument as a List<T> instead of a Collection<T>? What am I missing here?

Further adding to the confusion, this does work if I specify type in the call to map in the third overload:

public static <E, T> List<E> map(Function<? super T, ? extends E> fn, T[] coll) {
  return MyFns.<E,T>map(fn, Arrays.asList(coll));
}

The above compiles fine, even though this type should have been inferred anyway.

The first example compiled just fine under JDK 1.7, however if I use JDK 1.8, even if I specify -source 1.7, it still causes errors. Even if it is ambiguous under Java 8, the compiler should still work when set to language level 7, correct?

What am I doing wrong?

like image 590
Joel Croteau Avatar asked Apr 04 '16 21:04

Joel Croteau


1 Answers

I lifted this up from a comment because I wanted to explain it a little better.

I'm pretty sure this works kind of like this non-generic example:

static void m(Double a, Object b) {}
static void m(Object a, String b) {}

static {
    m(1.0, ""); // compiler error
}

That is, Function<? super T, E> is found to be more specific than Function<? super T, ? extends E>, for some reason.

One way to explain it is that Function<? super T, E> could be considered a subtype of Function<? super T, ? extends E>. This is normally true, for example List<String> is a subtype of List<? extends String>, but I'm not familiar enough with type inference to say for sure that this is definitely how the example works.

Another way to explain it intuitively would be that the non-wildcard E is always going to match its argument exactly so it can always be more specific.

Neither of those are really good enough for me, especially because you say this compiled for you under 1.7 and subtyping with wildcards existed then. This really comes down to how the type inference has changed.

I think the true answer is therefore specified by 18.5.4, which I just don't understand well enough right now to say for sure. (Also usually far too dense to quote from...)

Function<? super T, ? extends E> should not be castable to Function <? super T, E>, because the latter has a more specific type that doesn't include a wildcard.

This is correct intuition for an assignment, but when you're passing fn to the generic method, the wildcards are captured. See e.g. https://docs.oracle.com/javase/tutorial/java/generics/capture.html.

The ambiguity error is a bit perplexing but, disregarding the return type, both methods are applicable.

like image 82
Radiodef Avatar answered Oct 05 '22 23:10

Radiodef