Reputation: 2058
I've encountered a strange issue with the following code (well, not exactly this code):
public class CompilationProblems1 {
static Box<Class<? extends AlcoholicBewerage>> brokenBoxOfOneBeer = boxOf(Beer.class);
static Box<? extends Class<? extends AlcoholicBewerage>> boxOfOneBeer = boxOf(Beer.class);
static Box<Class<? extends AlcoholicBewerage>> boxOfBeerAndVodka = boxOf(Beer.class, Vodka.class);
interface AlcoholicBewerage {}
class Beer implements AlcoholicBewerage {}
class Vodka implements AlcoholicBewerage {}
static class Box<T> {}
static <E> Box<E> boxOf(E e) {
return new Box<E>();
}
static <E> Box<E> boxOf(E e1, E e2) {
return new Box<E>();
}
}
The first declaration brokenBoxOfOneBeer
gives a compilation error:
found : lt.tool.CompilationProblems1.Box<java.lang.Class<lt.tool.CompilationProblems1.Beer>>
required: lt.tool.CompilationProblems1.Box<java.lang.Class<? extends lt.tool.CompilationProblems1.AlcoholicBewerage>>
static Box<Class<? extends AlcoholicBewerage>> brokenBoxOfOneBeer = boxOf(Beer.class);
This error happens on OpenJDK 6, Eclipse and IntelliJ. I understand that it's a limitation of the type inferencer.
In the third case (boxOfBeerAndVodka
) the compiler is able to inference the correct covariant type because it has two subtypes to choose from, I believe. But why isnt' the compiler able to compile the first declaration, but is OK with the second one?
Upvotes: 1
Views: 92
Reputation: 183381
But why isnt' the compiler able to compile the first declaration, but is OK with the second one?
In both cases, the expression boxOf(Beer.class)
has type Box<Class<Beer>>
.
Box<Class<? extends AlcoholicBewerage>>
; since Box<Class<Beer>>
isn't a subtype of Box<Class<? extends AlcoholicBewerage>>
, this doesn't work. (Class<Beer>
is a subtype of Class<? extends AlcoholicBewerage>
, but due to invariance, this does not entail that Box<Class<Beer>>
is a subtype of Box<Class<? extends AlcoholicBewerage>>
.)Box<? extends Class<? extends AlcoholicBewerage>>
— which it does. Class<Beer>
is a subtype of Class<? extends AlcoholicBewerage>
, ergo Box<Class<Beer>>
is a subtype of Box<? extends Class<? extends AlcoholicBewerage>>
.That is, the exact same thing is happening in your declarations as would happen in:
List<Object> foo = new ArrayList<String>(); // doesn't work
List<? extends Object> bar = new ArrayList<String>(); // works
It just looks more complicated because instead of Object
and String
you have another level of generic types.
Upvotes: 1
Reputation: 6181
Type arguments are inferred based on actual arguments (Beer.class
) first (§15.12.2.7). If a type argument could not be inferred based on actual arguments, the context (brokenBoxOfBeer
) is taken into consideration (§15.12.2.8).
Thus, you can work around this issue as follows.
static <X, E extends X> Box<X> boxOf(E e) {
return new Box<X>();
}
Edit: I should note that this is a work-around and comes with its own set of problems. Nested invocations will no longer be inferred correctly.
Box<Box<Integer>> x = boxOf(boxOf(1)); // won't compile
Upvotes: 1