4

Java will let me do this:

public static class SomeType<I>{}

private static Map<Class<?>, Object> m = new HashMap<Class<?>, Object>();

public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
     return (List<SomeType<X>>)m.get(clazz);//warning
}

It will also let me do this:

public static class SomeType<I>{}

private static Map<Class<?>, List<?>> m = new HashMap<Class<?>, List<?>>();

public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
    return (List<SomeType<X>>)m.get(clazz);//warning
}

But it won't let me do this:

public static class SomeType<I>{}

private static Map<Class<?>, List<SomeType<?>>> m = new HashMap<Class<?>, List<SomeType<?>>>();

public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
    return (List<SomeType<X>>)m.get(clazz);//will not compile
}

unless I resort to the following workaround:

public static class SomeType<I>{}

private static Map<Class<?>, List<SomeType<?>>> m = new HashMap<Class<?>, List<SomeType<?>>>();

public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
    return (List<SomeType<X>>)(Object)m.get(clazz);//warning
}

So java makes it possible to explicitly convert from to Object to A<B<C>>, from A<?> to A<B<C>> but not from A<B<?>> to A<B<C>>.

why is that?

J Smith
  • 2,375
  • 3
  • 18
  • 36
  • related: [Is List a subclass of List? Why aren't Java's generics implicitly polymorphic?](http://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-p) – Paul Bellora Jul 16 '13 at 15:29
  • @JSmith Please don't just delete your questions after you got an answer for it. Accept the answer instead or at least explain why you want to delete the question... – Baz Jul 18 '13 at 19:49

4 Answers4

3

It seems, that your third example violates first JLS 5.5.1 rule:

If S is a class type:

If T is a class type, then either |S| <: |T|, or |T| <: |S|. Otherwise, a compile-time error occurs.

Furthermore, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types (§4.5), and that the erasures of X and Y are the same, a compile-time error occurs.

Indeed, let S be List<SomeType<?>>> and T be List<SomeType<X>>. These S and T are provably distinct parameterized types, because <?> does not equal to <X>. At the same time their erasures are the same, just: List and List.

So, according to specs this leads to compile-time error.

When your firstly cast m.get(...) to Object, you do not violate mentioned condition: Object and List<SomeType<X>> do not have same erasures, and furthermore, |List<SomeType<X>>| <: |Object|

P.S. as for List<?> case, this is also doesn't violate mentioned rule, because |List<SomeType<X>>| <: |List<?>|.

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
2

Java will not compile type casts that provably cannot succeed (assuming the things are the type they are declared as, and assuming that the value is not null). For a type cast to be possible to succeed, it must be (theoretically) possible to have a non-null type that is a subtype of both types.

  • Object to A<B<C>>: It is possible for this to succeed. For example, type A<B<C>> is a subtype of both.

  • A<?> to A<B<C>>: It is possible for this to succeed. For example, type A<B<C>> is a subtype of both.

  • A<B<?>> to A<B<C>>: It is not possible for this to succeed. i.e. there cannot exist a type that is a subtype of both.

To see why for the last one, recall that for parameterized types, Foo<A> cannot be a subtype of Foo<B> if A and B are different and neither is a wildcard. So consider A<B<?>>. Its parameter, B<?> is not a wildcard (it is an actual type; it is not ?, ? extends something, or ? super something).

So the only types that can be a subtype of A<B<?>> is itself, and SubclassOfA<B<?>>. Same thing applies to A<B<C>>: the only types that can be a subtype of A<B<C>> is itself, and SubclassOfA<B<C>>.

So can you see how it is not possible to have a type that is a subtype of both?

newacct
  • 119,665
  • 29
  • 163
  • 224
1

Because it is not type-safe. An example:

List<Class<?>> classes = new ArrayList<Class<?>>();
classes.add(String.class);

// This is invalid!
List<Class<Boolean>> casted = (List<Class<Boolean>>) classes;
// We've somehow assigned a Class<String> to a Class<Boolean>!
Class<Boolean> invalid = casted.get(0);

Also, while Java will allow a cast from Object to List<Class<Boolean>> and from List<?> to List<Class<Boolean>>, both will produce an unchecked warning. They are not errors because they could conceivably be type-safe, whereas List<Class<?>> to List<Class<Boolean>> cannot be type-safe.

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
0

This

public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
    return (List<SomeType<X>>)m.get(clazz);//will not compile
}

It not allowed because, the compiler do not know anything about SomeType.

You have create an method with type parameter X and parameter Class, but in result you expect to return a List that store SomeType of X type. You do not specified how to pass from X to SomeType. The map in get expect Object, but return the declared type witch is SomeType<?>.

As ? in this case is more less the same as X, you can do something like this.

To fix that you need to say, that that X must be, if you want to use it in return type.

public static <X extends SomeType<?>> List<? super X> getList(Class<X> clazz)
{
    return  m.get(clazz); //No error, no warring ;-)
}