Java Generics Inconsistent Behavior?

Why is the first method compiled and the second not? The generics for Set and ImmutableSet.Builder same, and the type signatures for their add methods are the same.

 import java.util.Set; import java.util.HashSet; import com.google.common.collect.ImmutableSet; public class F { public static ImmutableSet<? extends Number> testImmutableSetBuilder() { ImmutableSet.Builder<? extends Number> builder = ImmutableSet.builder(); Number n = Integer.valueOf(4); builder.add(n); return builder.build(); } public static Set<? extends Number> testJavaSet() { Set<? extends Number> builder = new HashSet<Number>(); Number n = Integer.valueOf(4); builder.add(n); return builder; } } 

I am using javac version 1.7.0_25 for the build. I get the following error in the second method, but not in the first. I believe that I should get an error in both cases, since it is not true to put Number in the collection ? extends Number ? extends Number .

 error: no suitable method found for add(Number) builder.add(n); ^ method Set.add(CAP#1) is not applicable (actual argument Number cannot be converted to CAP#1 by method invocation conversion) where CAP#1 is a fresh type-variable: CAP#1 extends Number from capture of ? extends Number 
+7
java generics
source share
4 answers

Indeed, add(E) not applicable, but the question is less clear for the add(E...) method:

The Java language specification defines :

Method m is an applicable variable-degree method if and only if all of the following conditions are true:

  • For 1 ≀ i <n, the type ei, Ai, can be converted by converting the method call to Si.

  • If k β‰₯ n, then for n ≀ i ≀ k the type ei, Ai can be converted by converting the method call to the component type Sn.

  • ...

In our case, Sn is capture-of-? extends Number[] capture-of-? extends Number[] .

Now what is the type of component Sn? Unfortunately, JLS does not provide a formal definition for this term. In particular, he did not explicitly say whether the component type is a compile-time type (which does not need to be verifiable) or a runtime type (which should be). In the case of the first type of component will capture-or-? extends Number capture-or-? extends Number , which cannot accept the conversion of calling the Number through method. In the case of the latter, the component type will be Number , which obviously can.

It seems that the eclipse compiler developers used the former definition, while javac developers used the latter.

Two facts seem to imply that the type of the component is indeed a runtime type. First, spec requires :

If the type of the assigned value is not compatible with the destination (Β§5.2) with the type of the component, an ArrayStoreException is thrown.

If the type of the array component could not be verified (Β§4.7), the Java virtual machine could not perform the storage check described in the previous paragraph. This is why the expression to create an array with a non-resettable element type is forbidden (Β§15.10). You can declare an array type variable whose element type is not restored, but assigning the result of an array creation expression to a variable will necessarily lead to an uncontrolled warning (Β§5.1.9).

and secondly, the evaluation of the execution of a method invocation expression aimed at the method of the arity variable is determined as follows:

If m is called with k β‰  n actual argument expressions or if m is called with k = n actual argument expressions, and the expression type k'th of the argument is not an assignment compatible with T [], then the list of arguments (e1, ... , en-1, en, ..., ek) is evaluated as if it were written as (e1, ..., en-1, new | T [] | {en, ..., ek}) where | T [] | deletes (Β§4.6) T [].

An accidental reading of this paragraph may lead to the idea that the expression is not only evaluated, but also verified in this way, but the specification does not quite say that.

On the other hand, a rather strange concept is to use erasure to check the type of compilation time (although the specification requires this in some other cases).

To summarize, the observed differences between the eclipse compiler and javac seem to be based on a slightly different interpretation of the type checking rules for non-recoverable variable method arguments.

+1
source share

I think I started to figure out the answer. ImmutableSet.Builder the add method is overloaded, there is an alternative signature add(E... elements) . I ran javap -v in the resulting .class file, and I saw that this alternate method is the one that is actually called. Varargs elements is actually a Java array under covers, and Java arrays are covariant. Ie, in relation to this particular example, we call

 builder.add(n); 

Number n converted to a singleton array of type Number[] . But I do not know how this array is legally converted to an <? extends Number> <? extends Number> !

+1
source share

When do you say Set<? extends Number> builder; Set<? extends Number> builder; , this means that it might have something that extends Number , for example. Integer Now you put Number in builder.add(n); which is not an Integer . If you need to add something, you must do Set<? super Number> builder; Set<? super Number> builder; .

0
source share

There is something called the principle of GET and PUT. It is always recommended to follow it.

GET and PUT Principle

"use an extended wildcard when you only get values ​​from the structure, use a super character when you only add values ​​to the structure and don’t use a wildcard when you both get and put"

0
source share

All Articles