【问题标题】:Use class instance as generic type使用类实例作为泛型类型
【发布时间】:2015-05-07 07:27:05
【问题描述】:

有没有办法使用SomeClass.class 作为一个类型的泛型类型?

例如我有一个枚举:

enum MyEnum {
    FOO(String.class);
    private Class fooClass;
    private MyEnum(Class fooClass) {
        this.fooClass = fooClass;
    }
    public Class getFooClass(){
        return fooClass;
    }
}

然后在我的代码中的某个地方:

List<MyEnum.FOO.getFooClass()> list = new ArrayList<>();

【问题讨论】:

  • 你能再展示几行代码来说明你将如何使用这个list吗?泛型用于启用编译类型类型检查。如果在编译时组件类型未知,那么这里几乎没有什么可做的。
  • 我不确定您会如何处理 .class 类型的列表。如果我没记错的话,对于给定的类加载器,在运行时只有一个 class 对象对应于 class(无论您为类创建了多少对象)
  • @ChetanKinger:这不是类类型的列表。它是 List&lt;String&gt;List&lt;Number&gt; 或其他,基于枚举。 (与List&lt;Object&gt;List 相比,我不确定这样做的目的是什么)
  • @Thilo Java 是否允许首先以这种方式创建列表? List&lt;String.class&gt; 是否允许? List&lt;MyEnum.FOO.getFooClass()&gt; 在语法上是否可行?
  • 回复:cast。真的。我将它与Collections.checkedList(list, String.class) 混淆了(它针对假定的编译时类型进行运行时检查)。

标签: java class enums


【解决方案1】:

没有。 泛型在编译时评估,而此调用的值:

MyEnum.FOO.getFooClass()

仅在运行时才知道。

【讨论】:

    【解决方案2】:

    泛型类型在编译时使用,而不是在运行时使用 - 看看 Java 如何使用泛型擦除。结果,您不能这样做。

    你可以做的,这有点冗长,是在迭代列表时对枚举中的类使用 cast 方法,但即使那样你也不能进行任何动态通用分配。

    【讨论】:

      【解决方案3】:

      实际上,这可能有一点破解,这就是为什么 GSON 能够从 JSON 构造 List&lt;T&gt; - 它使用 TypeToken&lt;T&gt; 类来解析“类型化的通用超类" 的匿名 typetoken 实现。

      基于此,我认为应该可以从Class&lt;T&gt; 对象中获取List&lt;T&gt; 实例,如果我们能够以某种方式提取JSON 反序列化,并像@ 一样构造列表本身987654321@.

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

      我试图弄清楚 GSON 为获取超类做了什么。

      于是我找到了this

      //from https://stackoverflow.com/a/75345/2413303
      //from subclass
      T instance = ((Class)((ParameterizedType)this.getClass().
             getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
      

      然后我尝试了这个:

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

          MyTypeToken<String> typeToken = new MyTypeToken<String>() {
          };
          System.out.println(typeToken.factory().getClass().getName()); //works and prints java.lang.String
      

      但以下崩溃:

      public <T> List<T> getList(Class<T> clazz) {
          MyTypeToken<ArrayList<T>> token = new MyTypeToken<ArrayList<T>>() {};
          return token.factory();
      }
      

      Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
      

      所以我很确定 Google 遇到了这种情况,正如他们在 TypeToken 和更重要的是 Canonicalize method in $Gson$Types 中的魔法代码所表明的那样,他们使用内部 Sun 类的重新实现版本,例如 ParametrizedTypeImpl 等(因为他们有私人访问权限)。

      所以如果你像这样创建一个“枚举”:

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

      然后你会得到java.lang.String,但是如果你将它与ArrayList&lt;String&gt;一起使用,那么你会得到

      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)
      

      所以我猜你 可以 如果你不想窃取 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();
          }
      }
      

      而且有效。

      最好的一点是,你还可以将“enum”的参数替换为Class&lt;T&gt; 对象,并得到相同的结果。此外,您可以使列表的初始化成为静态方法的一部分。

      这是最终代码

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

      输出是

      hello
      

      但是是的,你不能这样做

      List<MyEnum.FOO.getFooClass()> 
      

      你需要知道你得到的对象是List&lt;String&gt;

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 匿名子类包含Type关于其父类的信息。
        • 好吧,你确实无法从foo.getClass() 得到它。没必要粗鲁。
        【解决方案5】:

        正如其他人已经说过的,你不能。 在这里查看更多信息: How To Instantiate a java.util.ArrayList with Generic Class Using Reflection

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-01-20
          • 2013-10-03
          • 2011-04-02
          • 2013-09-18
          • 1970-01-01
          相关资源
          最近更新 更多