Search code examples
javagenericsgeneric-programmingtype-inference

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?


Solution

  • 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.