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
intheAnyList
, but by doing so we have lost knowledge of their elements.When we use
? extends
, theList
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 aList<LinkedList<Float>>
? Or aList<Vector<Float>>
? We don't know.)We can put these together and have a
List<? extends List<?>>
. We don't know what type ofList
it has in it anymore, and we don't know the element type of thoseList
s 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 theList
s 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 ofList<?>
butList<List<String>>
is not a subtype ofList<List<?>>
. As shown before, this prevents us from compromising type safety by adding wrong elements to theList
.List<List<String>>
is a subtype ofList<? extends List<?>>
, because the bounded wildcard allows covariance. That is,? extends
allows the fact thatList<String>
is a subtype ofList<?>
to be considered.List<? extends List<?>>
is in fact a shared supertype:List<? extends List<?>>
╱ ╲
List<List<?>> List<List<String>>
In review
Map<Integer, List<String>>
accepts onlyList<String>
as a value.Map<?, List<?>>
accepts anyList
as a value.Map<Integer, List<String>>
andMap<?, List<?>>
are distinct types which have separate semantics.- One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
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 List
s 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 beabstract
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
What Does Maven Update Project Do in Eclipse
Access "This" from Java Anonymous Class
Apache Httpclient 4.0.3 - How to Set Cookie with Sessionid for Post Request
Detect If Java Application Was Run as a Windows Admin
Is Spring Default Scope Singleton or Not
Does Java Guarantee That Object.Getclass() == Object.Getclass()
Eventlisteners Using Hibernate 4.0 with Spring 3.1.0.Release
How to Display Bar Value on Top of Bar Javafx
Java Array with More Than 4Gb Elements
Apache Spark - Foreach VS Foreachpartition When to Use What
How to Unzip Files Recursively in Java
Making Spring-Data-Mongodb Multi-Tenant
How to Iterate Over a Priorityqueue
Making Spring-Data-Mongodb Multi-Tenant
Create Simple Pojo Classes (Bytecode) at Runtime (Dynamically)