5

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?

Victor
  • 743
  • 1
  • 5
  • 16
  • Java always chooses the 'most specific' overload. Clearly the same logic applies here too. – user207421 Sep 09 '17 at 01:00
  • And what does `dst.addAll(src);` show? – Elliott Frisch Sep 09 '17 at 01:02
  • The relevant part about how the method is selected is found in the JLS, particularly, the part about the "most specific method": https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5 (it's one of the not-so-easy-to-read parts of the JLS, though...) – Marco13 Sep 09 '17 at 01:02
  • (I'd say this question is certainly a duplicate of another, but I'm not sure which one to choose... (maybe ... (warning: lame pun ahead!) ... the *most specific one*? ... Hehe...) – Marco13 Sep 09 '17 at 01:05
  • 2
  • @ElliottFrisch If added to `main`, `boolean java.util.List.addAll(Collection extends A> c)`; if added to `copy`, `boolean java.util.List.addAll(Collection extends ? super T> c)` – Victor Sep 12 '17 at 02:40

1 Answers1

5

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.

Community
  • 1
  • 1
meriton
  • 68,356
  • 14
  • 108
  • 175
  • I'm rather impressed with how quickly you isolated the relevant rule in that _absolute fustercluck_ that is the JLS and managed to explain in concise, _comprehensible_ language exactly how it applies to OP's example. Truly exemplary. – Mike Strobel Sep 09 '17 at 02:14
  • 1
    @MikeStrobel - I challenge you to find *any* programming language specification (for a non-trivial PL) that is *both* rigorous *and* easy for the layman to read. (And if you think the JLS is bad ... try understanding the original Algol-68 spec.) – Stephen C Sep 09 '17 at 02:31
  • @Stephen-C I'm afraid I must decline your challenge, as my little apartment only has so many tables I can flip over. – Mike Strobel Sep 09 '17 at 02:38
  • Perhaps you should get out more :-) – Stephen C Sep 09 '17 at 02:43