Use an instance of a class as a generic type

Can it be used SomeClass.class

as a typical 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<>();

      

+3


source to share


5 answers


Not. Generics are evaluated at compile time, and the meaning of this call is:

MyEnum.FOO.getFooClass()

      



known only at runtime.

+3


source


Generic types are used at compile time, not runtime. See how Java uses generic erasure. As a result, you cannot do this.



What you can do, which is quite a bit more verbose, is using the cast method on the class you have from the enum while you iterate over the list, but even then you won't be able to do a dynamic general purpose.

+2


source


In fact, this is possible with a little hack, so GSON can build List<T>

from JSON - it uses a class TypeToken<T>

to resolve the "typed generic superclass" msgstr "anonymous type implementation.

Based on this, I decided that I should get an instance List<T>

from the object Class<T>

if we somehow extract the JSON deserialization and just build the list itself, like this .

//from /questions/1497862/deserialise-a-generic-list-in-gson/4563814#4563814
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 get the superclass.

So I found this :

//from /questions/15408/create-instance-of-generic-type-in-java/112382#112382
//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();
}

      

from

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 this, as evidenced by their magic code in the TypeToken and more importantly the Canonicalize Method in $ Gson $ Types , where they use overridden versions of Sun's internal classes like ParametrizedTypeImpl

and the like. (since they have private access).

So, if you do an "enumeration" 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>

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 what I think you can do if you don't want to steal the internal GSON 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.

Best of all, you can also replace the parameter " enum

" with an object Class<T>

and get the same result. Alternatively, you can make the list initialization 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 conclusion

hello

      

But yes, you cannot do something like

List<MyEnum.FOO.getFooClass()> 

      

You need to know what the object you are receiving is List<String>

.

+1


source


This is not possible due to the obvious fact: the value of the expression FOO.getFooClass()

is evaluated runtime

, while all generic files are compile-time

processed, and runtime

there is no generic type information in it.

0


source


As others have said, you cannot. Take a look here for more information: How to create a java.util.ArrayList with a generic class using Reflection

0


source







All Articles