Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Java handle ambiguous type inference for generics?

In this code, T can be A, B, C, or D, but Eclipse shows that it is D.

static class A { }
static class B extends A { }
static class C extends B { }
static class D extends C { }
static <T> void copy(List<? super T> dst, List<? extends T> src) {
    for (T t : src)
        dst.add(t);
}
public static void main(String[] args) {
    List<A> dst = new ArrayList<>();
    List<D> src = new ArrayList<>();
    copy(dst, src); // Eclipse shows T is D
}

Is there any rule for how type inference is done and why it selects D?

like image 870
Victor Avatar asked Sep 09 '17 00:09

Victor


People also ask

Does Java support type inference?

Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable.

How does type inference work in Java?

Type inference represents the Java compiler's ability to look at a method invocation and its corresponding declaration to check and determine the type argument(s). The inference algorithm checks the types of the arguments and, if available, assigned type is returned.

What does it mean to use a generic mechanism in Java?

In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.

Which one of the following options is the correct name for empty type parameter?

It is called generics.


1 Answers

Is there any rule for how type inference is done

Yes, the entire 18th chapter of the Java Language Specification is dedicated to this topic :-)

and why it selects D?

I think the following rule is responsible for that:

If the bound set does not contain a bound of the form G<..., αi, ...> = capture(G<...>) for all i (1 ≤ i ≤ n), then a candidate instantiation Ti is defined for each αi:

  • If αi has one or more proper lower bounds, L1, ..., Lk, then Ti = lub(L1, ..., Lk) (§4.10.4).

  • Otherwise, if the bound set contains throws αi, and the proper upper bounds of αi are, at most, Exception, Throwable, and Object, then Ti = RuntimeException.

  • Otherwise, where αi has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk) (§5.1.10).

The bounds α1 = T1, ..., αn = Tn are incorporated with the current bound set.

If the result does not contain the bound false, then the result becomes the new bound set, and resolution proceeds by selecting a new set of variables to instantiate (if necessary), as described above.

In plain english, when trying out possible values for a type parameter, the compiler first tries the lower bound, and uses that one if it fits.

In our case, the constraint set says that D extends T and D extends A, so the lower bound for T is D, so D is the first candidate substitution.

The compiler than verifies whether D fits by assuming that T = D, which simplifies that constraint set to D extends D and D extends A, both of which are known to be true.

Therefore, the compiler uses D.

like image 152
meriton Avatar answered Sep 29 '22 00:09

meriton