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<>();
7
  • 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. Commented May 7, 2015 at 7: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) Commented May 7, 2015 at 7:37
  • @ChetanKinger: It's not a list of class types. It is a List<String> or a List<Number> or whatever, based on the enum. (I'm not sure what the purpose of this is, compared to just List<Object> or List) Commented May 7, 2015 at 7:39
  • @Thilo Does Java allow creating a list in such a way in the first place? Is List<String.class> allowed? Is List<MyEnum.FOO.getFooClass()> even syntactically possible? Commented May 7, 2015 at 7:40
  • 1
    Re: cast. True. I confused it with Collections.checkedList(list, String.class) (which does the runtime check against presumed compile-time type). Commented May 7, 2015 at 7:45

5 Answers 5

3

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

MyEnum.FOO.getFooClass()

is only known at runtime.

Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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

Comments

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.

2 Comments

Anonymous subclasses contain Type information about their parent, though.
well, it's true that you can't get it from foo.getClass(). No need to be rude.
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.