Java Nested Generic Type

Java nested generic type

Fundamentally, List<List<?>> and List<? extends List<?>> have distinct type arguments.

It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.

Understanding semantic differences

Generally speaking, the wildcard ? represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.

For the moment, let's simplify the example by using List instead of Map.

  • A List<List<?>> holds any kind of List with any type argument. So i.e.:

    List<List<?>> theAnyList = new ArrayList<List<?>>();

    // we can do this
    theAnyList.add( new ArrayList<String>() );
    theAnyList.add( new LinkedList<Integer>() );

    List<?> typeInfoLost = theAnyList.get(0);
    // but we are prevented from doing this
    typeInfoLost.add( new Integer(1) );

    We can put any List in theAnyList, but by doing so we have lost knowledge of their elements.

  • When we use ? extends, the List holds some specific subtype of List, but we don't know what it is anymore. So i.e.:

    List<? extends List<Float>> theNotSureList =
    new ArrayList<ArrayList<Float>>();

    // we can still use its elements
    // because we know they store Float
    List<Float> aFloatList = theNotSureList.get(0);
    aFloatList.add( new Float(1.0f) );

    // but we are prevented from doing this
    theNotSureList.add( new LinkedList<Float>() );

    It's no longer safe to add anything to the theNotSureList, because we don't know the actual type of its elements. (Was it originally a List<LinkedList<Float>>? Or a List<Vector<Float>>? We don't know.)

  • We can put these together and have a List<? extends List<?>>. We don't know what type of List it has in it anymore, and we don't know the element type of those Lists either. So i.e.:

    List<? extends List<?>> theReallyNotSureList;

    // these are fine
    theReallyNotSureList = theAnyList;
    theReallyNotSureList = theNotSureList;

    // but we are prevented from doing this
    theReallyNotSureList.add( new Vector<Float>() );
    // as well as this
    theReallyNotSureList.get(0).add( "a String" );

    We've lost information both about theReallyNotSureList, as well as the element type of the Lists inside it.

    (But you may note that we can assign any kind of List holding Lists to it...)

So to break it down:

//   ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List

The Map works the same way, it just has more type parameters:

//  ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument

Why ? extends is necessary

You may know that "concrete" generic types have invariance, that is, List<Dog> is not a subtype of List<Animal> even if class Dog extends Animal. Instead, the wildcard is how we have covariance, that is, List<Dog> is a subtype of List<? extends Animal>.

// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}

// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();

// all parameterized Lists are subtypes of List<?>
List<?> b = a;

So applying these ideas to a nested List:

  • List<String> is a subtype of List<?> but List<List<String>> is not a subtype of List<List<?>>. As shown before, this prevents us from compromising type safety by adding wrong elements to the List.
  • List<List<String>> is a subtype of List<? extends List<?>>, because the bounded wildcard allows covariance. That is, ? extends allows the fact that List<String> is a subtype of List<?> to be considered.
  • List<? extends List<?>> is in fact a shared supertype:

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

In review

  1. Map<Integer, List<String>> accepts only List<String> as a value.
  2. Map<?, List<?>> accepts any List as a value.
  3. Map<Integer, List<String>> and Map<?, List<?>> are distinct types which have separate semantics.
  4. One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
  5. Map<?, ? extends List<?>> is a shared supertype which imposes safe restrictions:

            Map<?, ? extends List<?>>
    ╱ ╲
    Map<?, List<?>> Map<Integer, List<String>>

How the generic method works

By using a type parameter on the method, we can assert that List has some concrete type.

static <E> void test(Map<?, List<E>> m) {}

This particular declaration requires that all Lists in the Map have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.

For example, this kind of declaration might be useful for some kind of accumulation:

static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();

for(List<E> value : m.values()) {
result.addAll(value);
}

return result;
}

We can't call put on m because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are all List with the same element type.

Just for kicks

Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the List:

static <E> void test(Map<?, ? extends List<E>> m) {}

We would be able to call it with something like a Map<Integer, ArrayList<String>>. This is the most permissive declaration, if we only cared about the type of E.

We can also use bounds to nest type parameters:

static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}

This is both permissive about what we can pass to it, as well as permissive about how we can manipulate m and everything in it.


See also

  • "Java Generics: What is PECS?" for the difference between ? extends and ? super.
  • JLS 4.10.2. Subtyping among Class and Interface Types and JLS 4.5.1. Type Arguments of Parameterized Types for entry points to the technical details of this answer.

Using nested generic types in HashMap

You can't declare a map in this way. Basically, Java does not have an expressive-enough type system for this.

But luckily, the language provides an escape hatch from using the type system, in the form of casts. Basically, casts are a way to provide type information that the compiler doesn't have; the onus is then on you, however, to make sure that it actually is type safe.

Firstly, declare the map with wildcard types:

private final Map<A<?>,B<?>> map;

Then, only put key/value pairs into the map which do meet the constraint:

<T> void put (A<T> key, B<T> value) {
map.put(key, value);
}

And then cast when you get the element out again:

@SuppressWarnings("unchecked") // Safe
<T> B<T> get(A<T> key) {
return (B<T>) map.get(key);
}

The point is that you can know more about the types than the compiler. Provided you take care to only put in safely-castable pairs, it is safe to cast them. (You also need to ensure that the equals method takes the type into account, so no A<T> equals A<S> unless S == T).

This is basically a "type-safe heterogeneous container", as described in Effective Java (Item 33 in 3rd Edition).

I am wondering if there is a more elegant way that does not require me to cast every value I retreive.

For one thing, you aren't actually doing a cast in the get method at runtime: that's what an unchecked cast means.

For another, generics introduces loads of casts - basically generics is just a way to avoid inserting casts manually. So if there were a problem with the casts (such as performance), you'd already observe this in lots of places.

To paraphrase Kubrick: stop worrying, and love the cast.

Using nested generics in java

There is unknown for compiler type of T. Since the method getName comes from NamedClass, you have to do the same either in the method `ANamedGridHelper::mapProperties:

public class ANamedGridHelper<T extends NamedClass> {
public Column<T, ?> mapProperties(Grid<T> grid, T bo) {
return grid.addColumn(NamedClass::getName).setId("name");
}
}

Few notes:

  • A class having abstract method must be abstract as well.
  • An abstract method has no body - is without implementation and awaits to be overridden.

I had to also add the to the Grid parameter of the method, now it works as expected.

Nested generic in a generic class

You can't get rid of it. The second U is not redundant. You want the compiler to interpret the first U as a type parameter, but it doesn't. You could also have written this:

class EventThing<T extends AbstractThing<Double>>

Note that Double in this case is a concrete class, and not a type parameter. Compare this to the following:

class EventThing<T extends AbstractThing<U>>

Note that this has the exact same form as the first line of code above. How is the compiler supposed to know that in the first case, Double is meant as a concrete class, while in the second case, U is meant as a type parameter?

The compiler can't know that, and treats the U as a concrete class, just like the Double in the first line. The only way to let the compiler know that U is a type parameter is to specify it as such:

class EventThing<T extends AbstractThing<U>, U>

Java nested generic type mismatch

If you want to be able to call fourth with a List<List<String>> argument, then you'll need to change your signature to this:

private static void fourth(List<? extends List<?>> a){
System.out.println("List of a List of anything ");
}

The above will work because unlike List<List<?>>, List<? extends List<?>> is compatible with List<List<String>>. Think of it this way:

List<List<String>> original = null;
List<? extends List<?>> ok = original; // This works
List<?> ok2 = original; // So does this
List<List<?>> notOk = original; // This doesn't

List<Integer> original = null;
List<? extends Number> ok = original; // This works
List<?> ok2 = original; // So does this
List<Number> notOk = original; // This doesn't

The reasoning is simple. If you had

private static void fourth(List<List<?>> a) {
List<?> ohOh = Arrays.asList(new Object());
a.add(ohOh);
}

And then if you could call that method as such:

List<List<String>> a = new ArrayList<List<String>>();
fourth(a);
String fail = a.get(0).get(0); // ClassCastException here!

Nested generic types in static method

public static <T, K> T<K>

Your T has no bounds, meaning, T can be anything. It could be String.

String has no generics, so how can T<K> make sense? It doesn't, hence, java doesn't let you compile this.

I guess you could conceive of the notion of: "T is some specific type, could be anything, as long as it has exactly 1 generics param", but java doesn't have this, and never will, because that is structural type and java doesn't do that.

However, note that a generics param can be any type, notably include types that are themselves parameterized. Here is a trival example:

public static <T> T coalesce(T a, T b) {
return a == null ? b : a;
}

This method can be used like so:

String a = null;
String b = "Hello";

coalesce(a, b).toLowerCase();

There is absolutely no problem feeding it 2 List<String>, at which point the expression coalesce(listA, listB) would be of type List<String>. And that's just with <T>, not with this <T, K> stuff.

I don't quite know what jsonToObjectType is supposed to do, but assuming that it is supposed to take a string that contains JSON + some super-type-token (you can search the web for that term), which I'm 99.9% certain you have, then just remove K from it all, and you get precisely what you wanted:

public static <T> T jsonToObjectType(String json, TypeReference<T> type) {
// code here
}

and you can call it like so:

String json = "[\"Hello\", \"World!\"]";
List<String> list = jsonToObjectType(json, new TypeReference<List<String>>() {});

and it'll compile without warnings or errors and works.

How to keep generic type of nested generics with class tokens

The generics kung fu you need to make your ListProp class compile is this line:

super((Class<List<T>>)(Class<?>)List.class); // compiles

Attempting to cast directly from List.class to Class<List<T>>:

super((Class<List<T>>)List.class); //compile error

results in a compile error:

Inconvertible types; cannot cast 'java.lang.Class' to 'java.lang.Class>

But if you first cast to typed class Class<?>, albeit an unknown type, you can then cast it the desired typed class.

The full compilable ListProp class is then.

class ListProp<T> extends Prop<List<T>> {
Class<T> subtype;
public ListProp(Class<T> type) {
super((Class<List<T>>)(Class<?>)List.class); // magic double cast
subtype = type;
}
}

Something else you may consider, in case you need special code for creating/returning a list, is a typed getter for t on Prop:

public T getT() {
return t;
}

which you can then covariantly override in ListProp to return a List<T>

@Override
public List<T> getT() {
return Arrays.asList(subtype.newInstance()); // or whatever
}

It should be noted that you only need the class token if your implementation uses the class, which is not shown in your example code. If you don't actually use the class token, you can let type inference do the typing for you.



Related Topics



Leave a reply



Submit