dm3
dm3

Reputation: 2058

Curious type error in java generics

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

Answers (2)

ruakh
ruakh

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

  • The first declaration needs it to have type 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>>.)
  • The second declaration needs it to have type 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

Ben Schulz
Ben Schulz

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

Related Questions