Actually, this is possible with a bit of a hack, which is why GSON is able to construct List<T>
from JSON - it uses a TypeToken<T>
class to resolve the "typed generic superclass" of the anonymous typetoken implementation.
Based on that, I figured it should be possible to obtain a List<T>
instance from a Class<T>
object if we can somehow extract the JSON deserialization, and just construct the list itself like so.
//from https://stackoverflow.com/a/18321048/2413303
private <T> List<T> getList(Class<T> elementType) {
...
TypeToken<List<T>> token = new TypeToken<List<T>>() {}
.where(new TypeParameter<T>() {}, elementType);
List<T> something = gson.fromJson(data, token); //this would have needed to be replaced
...
}
I tried to figure out what GSON does to obtain the superclass.
So I found this:
//from https://stackoverflow.com/a/75345/2413303
//from subclass
T instance = ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
And then I tried this:
public static abstract class MyTypeToken<T> {
public T factory() {
try {
Type type = getClass().getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) type;
return ((Class<T>) paramType.getActualTypeArguments()[0]).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
And
MyTypeToken<String> typeToken = new MyTypeToken<String>() {
};
System.out.println(typeToken.factory().getClass().getName()); //works and prints java.lang.String
But the following crashes:
public <T> List<T> getList(Class<T> clazz) {
MyTypeToken<ArrayList<T>> token = new MyTypeToken<ArrayList<T>>() {};
return token.factory();
}
with
Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
So I'm pretty sure Google ran into that, as indicated by their magic code in TypeToken and more importantly this Canonicalize method in $Gson$Types, where they use reimplemented versions of the internal Sun classes such as ParametrizedTypeImpl
and the like (because they have private access).
So if you make an "enum" like this:
public static abstract class MyEnum<T> {
public static final MyEnum<String> FOO = new MyEnum<String>(new MyTypeToken<String>() {}) {};
protected MyTypeToken<T> typeToken;
private MyEnum(MyTypeToken<T> typeToken) {
this.typeToken = typeToken;
}
public MyTypeToken<T> getTypeToken() {
return typeToken;
}
}
public void execute() {
String string = MyEnum.FOO.getTypeToken().factory();
System.out.println(string.getClass().getName());
}
Then you get java.lang.String
, but if you use it with ArrayList<String>
, then you get
Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
at com.company.Main$TypeToken.factory(Main.java:19)
at com.company.Main.execute(Main.java:49)
at com.company.Main.main(Main.java:55)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
So I guess what you can do if you don't want to steal the GSON internal code is this (use GSON and its own TypeToken):
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.util.ArrayList;
import java.util.List;
/**
* Created by EpicPandaForce on 2015.05.06..
*/
public class Main {
public static abstract class MyEnum<T> { //typed `enum`-like structure
public static final MyEnum<String> FOO = new MyEnum<String>(new TypeToken<String>() {}) {};
protected TypeToken<T> typeToken;
private MyEnum(TypeToken<T> typeToken) {
this.typeToken = typeToken;
}
public TypeToken<T> getTypeToken() {
return typeToken;
}
}
public static <T> TypeToken<ArrayList<T>> getListToken(TypeToken<T> typeToken) {
return new TypeToken<ArrayList<T>> () {};
}
public void execute() {
Gson gson = new Gson();
List<String> list = gson.fromJson("[]", getListToken(MyEnum.FOO.getTypeToken()).getType()); //construct empty list using GSON
list.add("hello");
System.out.println(list.get(0)); //writes out hello
}
public static void main(String[] args) {
Main main = new Main();
main.execute();
}
}
And it works.
The best thing is that you can also replace the "enum
"'s parameter to a Class<T>
object, and get the same result. Also, you can make the initialization of the list be part of a static method.
This is the final code:
public class Main {
public static abstract class MyEnum<T> {
public static final MyEnum<String> FOO = new MyEnum<String>(String.class) {};
protected Class<T> typeToken;
private MyEnum(Class<T> clazz) {
this.typeToken = clazz;
}
public Class<T> getClazz() {
return typeToken;
}
}
public static class ListFactory {
private static Gson gson;
static {
gson = new Gson();
}
private static <T> TypeToken<ArrayList<T>> getListToken(Class<T> typeToken) {
return new TypeToken<ArrayList<T>> () {};
}
public static <T> List<T> getList(Class<T> clazz) {
return gson.fromJson("[]", getListToken(clazz).getType());
}
}
public void execute() {
List<String> list = ListFactory.getList(MyEnum.FOO.getClazz());
list.add("hello");
System.out.println(list.get(0));
}
public static void main(String[] args) {
Main main = new Main();
main.execute();
}
}
And the output is
hello
But yes, you cannot do something like
List<MyEnum.FOO.getFooClass()>
You need to know that the object you are getting is a List<String>
.