简体   繁体   English

使用类实例作为泛型类型

[英]Use class instance as generic type

Is there any way of using SomeClass.class as the generic type for a type? 有没有办法使用SomeClass.class作为类型的泛型类型?

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<>();

No. Generics are evaluated at compile time while the value of this call: 在编译时评估泛型,而此调用的值为:

MyEnum.FOO.getFooClass()

is only known at runtime. 只在运行时才知道。

Generic types are used at compile time, not run time - take a look at how Java uses erasure with generics. 通用类型在编译时使用,而不是在运行时使用 - 看看Java如何使用泛型擦除。 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. 你可能能够做到,这是更详细的不少,就是用铸造方法你从枚举有,而你遍历列表中的类,但即使如此,你将无法做任何动态通用分配。

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. 其实,这可能的一个黑客位,这就是为什么GSON是能够构建的List<T>从JSON -它采用了TypeToken<T>类来解决“输入通用父”匿名typetoken实施。

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 . 基于此,我认为应该可以从Class<T>对象获取List<T>实例,如果我们可以以某种方式提取JSON反序列化,并且只是像这样构造列表本身。

//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. 我试图找出GSON为获得超类所做的工作。

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 ). 所以我很确定Google会遇到这种情况,正如他们在TypeToken中的魔术代码以及更重要的$ Gson $ Types中的Canonicalize方法所表明的那样,他们使用内部Sun类的重新实现版本,例如ParametrizedTypeImpl等(因为他们有私人访问权限 )。

So if you make an "enum" like this: 所以,如果你像这样制作一个“enum”:

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 然后你得到java.lang.String ,但是如果你将它与ArrayList<String> ,那么你得到了

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): 所以我想 如果你不想窃取GSON内部代码就 可以 做到这一点(使用GSON和它自己的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. 最好的是你也可以将“ enum ”的参数替换为Class<T>对象,并获得相同的结果。 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> . 您需要知道您获得的对象是List<String>

由于明显的事实,这是不可能的: FOO.getFooClass()表达式的值是runtime评估的,而所有泛型都是compile-time处理的,并且在runtime没有关于泛型类型的任何信息。

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 在这里查看更多信息: 如何使用反射实例化具有泛型类的java.util.ArrayList

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM