28

A raw list converts to List<?> just fine. Why can't a list of raw lists convert to a list of List<?>?

{   // works
    List raw = null;
    List<?> wild = raw;
}
{   // Type mismatch: cannot convert from List<List> to List<List<?>>
    List<List> raw = null;
    List<List<?>> wild = raw;
}

Backstory (to mitigate the xy problem):

An API I'm using returns List<JAXBElement>. I happen to know that it is always List<JAXBElement<String>>. I plan to loop and build my own List<String>, but I was trying to fix (but not suppress) the raw type compiler warning when I write List<JAXBElement> raw = api();.

I tried:

List<JAXBElement<?>> raw = api();
List<JAXBElement<?>> raw = (List<JAXBElement<?>>) api();

but these give the type mismatch error.

Interestingly, this gives no warning or error:

for (JAXBElement<?> e : api()) {
    // ...
}
Community
  • 1
  • 1
djeikyb
  • 4,470
  • 3
  • 35
  • 42
  • If you know it's always `List>`, cast to that, not to `List>` or `List>`. If you know all the types, stay away from the question mark. – Mike 'Pomax' Kamermans Nov 05 '14 at 20:44
  • If you want only to remove warnings you can use @SuppressWarnings("rawtypes") – Carlos Verdes Nov 05 '14 at 20:47
  • If you *know* it's a `List>` you can force the cast to be allowed by going through a raw type. `(List>)(List)api()` – user253751 Nov 05 '14 at 20:51
  • @djeikyb I edited my answser check it please, you will see that you can't avoid the warning. – Carlos Verdes Nov 05 '14 at 21:10
  • @Mike'Pomax'Kamermans that cast is an error: `Cannot cast from List to List>` (of course without an intermediary cast to Object or some such) – djeikyb Nov 05 '14 at 21:24
  • @djeikyb FYI the for loop example is a different case because it uses `Iterable` that will operate on the individual `JAXBElement` instances returned by api() so is allowed. – Mark Nov 05 '14 at 22:48
  • possible duplicate of [List vs List](http://stackoverflow.com/questions/6783316/list-vs-listobject) – Raedwald Nov 05 '14 at 22:54
  • @Raedwald I did come across that question. It's definitely related. I feel my question is sufficiently different because of the wildcard use. The answers here, especially Radiodef's (which I've not had time to thoroughly read), address the nuance (compare to the accepted answer on the possible dupe). – djeikyb Nov 05 '14 at 23:37
  • It's not a duplicate of that question, nor any that I've seen exactly. – Radiodef Nov 05 '14 at 23:42
  • 3
    It's because "unknown" != "anything" – user541686 Nov 07 '14 at 11:00

3 Answers3

34
// #1 (does compile)
List raw = null;
List<?> wild = raw;

// #2 (doesn't compile)
List<List> raw = null;
List<List<?>> wild = raw;

First let's sort out why these are actually unrelated assignments. That is, they're governed by different rules.

#1 is called an unchecked conversion:

There is an unchecked conversion from the raw class or interface type (§4.8) G to any parameterized type of the form G<T1,...,Tn>.

Specifically it is a special case of an assignment context just for this scenario:

If, after [other possible conversions] have been applied, the resulting type is a raw type, an unchecked conversion may then be applied.

#2 requires a reference type conversion; however the problem with it is that it is not a widening conversion (which is the kind of reference conversion that would be implicitly allowed without a cast).

Why is that? Well, this is specifically governed by the rules of generic subtyping and more specifically this bullet point:

Given a generic type declaration C<F1,...,Fn> (n > 0), the direct supertypes of the parameterized type C<T1,...,Tn>, where Ti (1 ≤ in) is a type, are all of the following:

  • C<S1,...,Sn>, where Si contains Ti (1 ≤ in).

This refers us to something the JLS calls containment, where to be a valid assignment, the arguments of the left-hand side must contain the arguments of the right-hand side. Containment largely governs generic subtyping since "concrete" generic types are invariant.

You may be familiar with the ideas that:

  • a List<Dog> is not a List<Animal>
  • but a List<Dog> is a List<? extends Animal>.

Well, the latter is true because ? extends Animal contains Dog.

So the question becomes "does a type argument List<?> contain a raw type argument List"? And the answer is no: although List<?> is a subtype of List, this relationship does not hold for type arguments.

There is no special rule that makes it true: List<List<?>> is not a subtype of List<List> for essentially the same reason List<Dog> is not a subtype of List<Animal>.

So because List<List> is not a subtype of List<List<?>>, the assignment is invalid. Similarly, you cannot perform a direct narrowing conversion cast because List<List> is not a supertype of List<List<?>> either.


To make the assignment you can still apply a cast. There are three ways to do it that seem reasonable to me.

// 1. raw type
@SuppressWarnings("unchecked")
List<List<?>> list0 = (List) api();

// 2. slightly safer
@SuppressWarnings({"unchecked", "rawtypes"})
List<List<?>> list1 = (List<List<?>>) (List<? extends List>) api();

// 3. avoids a raw type warning
@SuppressWarnings("unchecked")
List<List<?>> list2 = (List<List<?>>) (List<? super List<?>>) api();

(You can substitute JAXBElement for the inner List.)

Your use-case for this casting should be safe because List<List<?>> is a more restrictive type than List<List>.

  • The raw type statement is a widening cast then unchecked assignment. This works because, as shown above, any parameterized type can be converted to its raw type and vice-versa.

  • The slightly safer statement (named as such because it loses less type information) is a widening cast then narrowing cast. This works by casting to a common supertype:

        List<? extends List>
            ╱         ╲
    List<List<?>>     List<List>
    

    The bounded wildcard allows the type arguments to be considered for subtyping via containment.

    The fact that List<? extends List> is considered a supertype of List<List<?>> can be proven with transitivity:

    1. ? extends List contains ? extends List<?>, because List is a supertype of List<?>.

    2. ? extends List<?> contains List<?>.

    3. Therefore ? extends List contains List<?>.

    (That is, List<? extends List> :> List<? extends List<?>> :> List<List<?>>.)

  • The third example works in a way that's similar to the second example, by casting to a common supertype List<? super List<?>>. Since it doesn't use a raw type, we can suppress one less warning.


The non-technical summary here is that the specification implies that there is neither subtype nor supertype relationship between List<List> and List<List<?>>.

Although converting from List<List> to List<List<?>> should be safe, it is not allowed. (It's safe because both are a List that can store any kind of List, but a List<List<?>> imposes more restrictions on how its elements can be used after they are retrieved.)

There is unfortunately no practical reason this fails to compile except that raw types are strange and usage of them is problematic.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • 2
    I really appreciate the effort you put in to reference the jls alongside your syllogistic narrative – djeikyb Mar 16 '15 at 06:04
3

You cannot assign or cast it directly, because raw type List isn't the same as List<?>.

When using List type checking is ignored and you may use any generic method with any type. When using List<?> the compiler won't let you use methods with generic parameters.


Therefore you could either ignore the warnings:

@SuppressWarnings("rawtypes")

And/Or cast it explicitly with a workaround:

List<JAXBElement<String>> raw = (List<JAXBElement<String>>) ((Object)api());
Community
  • 1
  • 1
Christian Strempfer
  • 7,291
  • 6
  • 50
  • 75
0

If you want only to remove warnings you can use @SuppressWarnings("rawtypes").

Basically the problem is that the compiler treats the rawtype as a primitive Object previous generics so... an "old Object" is not a "generic Object" so... you can't cast them.

Read this from official doc: http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html

But if you assign a raw type to a parameterized type, you get a warning:

Box rawBox = new Box(); // rawBox is a raw type of Box Box intBox = rawBox; // warning: unchecked conversion You also get a warning if you use a raw type to invoke generic methods defined in the corresponding generic type:

Box stringBox = new Box<>(); Box rawBox = stringBox; rawBox.set(8); // warning: unchecked invocation to set(T) The warning shows that raw types bypass generic type checks, deferring the catch of unsafe code to runtime. Therefore, you should avoid using raw types.

Carlos Verdes
  • 3,037
  • 22
  • 20