Reputation: 2540
Is there any way of using SomeClass.class
as the generic type for a type?
For example I have an enum:
enum MyEnum {
FOO(String.class);
private Class fooClass;
private MyEnum(Class fooClass) {
this.fooClass = fooClass;
}
public Class getFooClass(){
return fooClass;
}
}
And then somewhere in my code:
List<MyEnum.FOO.getFooClass()> list = new ArrayList<>();
Upvotes: 3
Views: 900
Reputation: 81539
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>
.
Upvotes: 1
Reputation: 2043
As others already said, you can't. Take a look here for further information : How To Instantiate a java.util.ArrayList with Generic Class Using Reflection
Upvotes: 0
Reputation: 34900
This is impossible due to obvious fact: value of FOO.getFooClass()
expression is runtime
evaluated, while all generics are compile-time
processed and in runtime
there is no any infomration about generic type.
Upvotes: 0
Reputation: 1061
No. Generics are evaluated at compile time while the value of this call:
MyEnum.FOO.getFooClass()
is only known at runtime.
Upvotes: 3
Reputation: 8202
Generic types are used at compile time, not run time - take a look at how Java uses erasure with generics. As a result, you can't do this.
What you may be able to do, which is quite a bit more verbose, is use the cast method on the class you have from the enum while you iterate over the list, but even then you wouldn't be able to do any dynamic generic assignment.
Upvotes: 2