Вопрос

So I have a Tuple2 class as follows:

public final class Tuple2<T1, T2> {
   private final T1 mItem1;
   private final T2 mItem2;

   public T1 getItem1() { return mItem1; }
   public T2 getItem2() { return mItem2; }

   public Tuple2(final T1 pItem1, final T2 pItem2) {
      mItem1 = pItem1;
      mItem2 = pItem2;
   }

   public static <TItem1, TItem2> Tuple2<TItem1, TItem2>
   Create(final TItem1 pItem1, final TItem2 pItem2) {
      return new Tuple2<>(pItem1, pItem2);
   }
}

and I'm trying to instantiate a List of Tuple2s, but the type inference seems way weird. I would expect I could do something like this:

// doesn't work
// note that Type2 extends Type1, and Type3 extends Type2
final List<Tuple2<String, Class<?>>> list = Arrays.asList(
   Tuple2.Create("1", Type1.class),
   Tuple2.Create("2", Type2.class),
   Tuple2.Create("3", Type3.class)
);

// still doesn't work
final List<Tuple2<String, Class<? extends Type1>>> list = Arrays.asList(
   Tuple2.Create("1", Type1.class),
   Tuple2.Create("2", Type2.class),
   Tuple2.Create("3", Type3.class)
);

Neither of these work if I cast to Class<?> or to Class<? extends Type1> either.

What it requires me to do instead is:

final List<Tuple2<String, ? extends Class<? extends Type1>>> list = Arrays.asList(
   Tuple2.Create("1", Type1.class),
   Tuple2.Create("2", Type2.class),
   Tuple2.Create("3", Type3.class)
);

But then if I want to define a reference to one of these Tuple2s I have to write:

final Tuple2<String, ? extends Class<? extends Type1>> item = list.get(0);

That is one ugly type name... Is there a way to simplify this? Why does it have to be a Tuple2 "of something that extends Class of something that extends Type1", rather than simply "of Class of something"?

The only simpler way I've found is using the Class raw type, which seems to be discouraged, and needs some casting:

final List<Tuple2<String, Class>> list = Arrays.asList(
   Tuple2.Create("1", (Class)Type1.class),
   Tuple2.Create("2", (Class)Type2.class),
   Tuple2.Create("3", (Class)Type3.class)
);
final Tuple2<String, Class> item = list.get(0);
Это было полезно?

Решение

This is the old generic invariance rearing its head again.

For each call to Create, the types are inferred as:

<String, Class<Type1>>
<String, Class<Type2>>
<String, Class<Type3>>

As you may know, a List<Dog> is not a List<Animal> and the same concept applies here. A Tuple2<String, Class<Type1>> is not a Tuple2<String, Class<?>> but it is a Tuple2<String, ? extends Class<?>>.

So for the three inferred Tuple2 types:

Tuple2<String, Class<Type1>>
Tuple2<String, Class<Type2>>
Tuple2<String, Class<Type3>>

The type they have in common is Tuple2<String, ? extends Class<? extends Type1>>.

As far as I know the only way around this is to supply a type witness for each call:

final List<Tuple2<String, Class<?>>> list = Arrays.asList(
   Tuple2.<String, Class<?>>Create("1", Type1.class),
   Tuple2.<String, Class<?>>Create("2", Type2.class),
   Tuple2.<String, Class<?>>Create("3", Type3.class)
);

That should work.

It seems like this should work:

final List<Tuple2<String, Class<?>>> list = Arrays.asList(
   Tuple2.Create("1", (Class<?>)Type1.class),
   Tuple2.Create("2", (Class<?>)Type2.class),
   Tuple2.Create("3", (Class<?>)Type3.class)
);

But it doesn't compile. The compiler does capture conversion and infers the Class<?> as Class<capture of ?>. So the inferred types you end up with are something like:

<String, Class<capture #1 of ?>>
<String, Class<capture #2 of ?>>
<String, Class<capture #3 of ?>>

And still three distinct types from each other.

I will be honest: I have no idea why it works that way, just that it does. I have been unable to find a definitive answer to that. The spec says this:

  • If Ti is a wildcard type argument [...] of the form ?, then Si is a fresh type variable [...].

Which seems to imply that capture of ? is considered as a new type and distinct from ?.

It seems to me that inference just has a gap with respect to the wildcard as of version 7.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top