3

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<>();
Mark Buikema
  • 2,211
  • 26
  • 51
  • 1
    Can you show a few more lines of code to make it clear how you would then use this `list`? Generics are to enable compile-type type checking. If the component type is not known at compile-time, then there is little to be done here. – Thilo May 07 '15 at 07:36
  • I am not sure what you would do with a List of `.class` types. If I am not wrong, there is only one `class` object corresponding to a `class` at runtime for a given classloader (no matter how many objects you create of your class) – CKing May 07 '15 at 07:37
  • @ChetanKinger: It's not a list of class types. It is a `List` or a `List` or whatever, based on the enum. (I'm not sure what the purpose of this is, compared to just `List` or `List`) – Thilo May 07 '15 at 07:39
  • @Thilo Does Java allow creating a list in such a way in the first place? Is `List` allowed? Is `List` even syntactically possible? – CKing May 07 '15 at 07:40
  • No, it's not allowed (syntactically or otherwise). And I don't quite understand what that would do. Best you can do is using the `cast` method as outlined by Steve's answer, or something like `Collections.emptyList()` (which returns whatever type you want). – Thilo May 07 '15 at 07:41
  • @Thilo According to Steve's answer, the cast method would be called on the reference to a `String` object right and not directly on a reference to `String.class` – CKing May 07 '15 at 07:43
  • 1
    Re: `cast`. True. I confused it with `Collections.checkedList(list, String.class)` (which does the runtime check against presumed compile-time type). – Thilo May 07 '15 at 07:45

5 Answers5

3

No. Generics are evaluated at compile time while the value of this call:

MyEnum.FOO.getFooClass()

is only known at runtime.

vap78
  • 967
  • 2
  • 11
  • 23
2

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.

Steve Chaloner
  • 7,954
  • 1
  • 20
  • 38
1

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>.

Community
  • 1
  • 1
EpicPandaForce
  • 71,034
  • 25
  • 221
  • 371
0

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.

Andremoniy
  • 31,241
  • 14
  • 110
  • 218
0

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

Community
  • 1
  • 1
francesco foresti
  • 1,914
  • 16
  • 21