【问题标题】:Java: how do I get a class literal from a generic type?Java:如何从泛型类型中获取类文字?
【发布时间】:2011-01-24 08:13:04
【问题描述】:

通常,我见过人们这样使用类字面量:

Class<Foo> cls = Foo.class;

但是如果类型是泛型的,例如列表?这工作正常,但有一个警告,因为 List 应该被参数化:

Class<List> cls = List.class

那么为什么不添加&lt;?&gt;?好吧,这会导致类型不匹配错误:

Class<List<?>> cls = List.class

我认为这样的事情会起作用,但这只是一个普通的语法错误:

Class<List<Foo>> cls = List<Foo>.class

如何静态获取Class&lt;List&lt;Foo&gt;&gt;,例如使用类文字?

可以使用@SuppressWarnings("unchecked")来摆脱第一个例子Class&lt;List&gt; cls = List.class中List的非参数化使用所引起的警告,但我宁愿不这样做。

有什么建议吗?

【问题讨论】:

    标签: java generics class literals


    【解决方案1】:

    你不能因为type erasure

    Java 泛型只不过是对象转换的语法糖。演示:

    List<Integer> list1 = new ArrayList<Integer>();
    List<String> list2 = (List<String>)list1;
    list2.add("foo"); // perfectly legal
    

    如果通过反射询问类的成员,则在运行时保留泛型类型信息的唯一实例是Field.getGenericType()

    这就是Object.getClass()有这个签名的原因:

    public final native Class<?> getClass();
    

    重要的部分是Class&lt;?&gt;

    换句话说,来自Java Generics FAQ

    为什么没有具体参数化类型的类文字?

    因为参数化类型没有精确的运行时类型表示。

    类文字表示Class 表示给定类型的对象。 例如,类文字 String.class 表示Class 表示类型的对象 StringClass 时返回的对象 方法 getClass 在 a 上调​​用 String 对象。类文字可以 用于运行时类型检查和 进行反思。

    参数化类型失去其类型 参数被翻译成 编译期间的字节码 过程称为类型擦除。作为一个 类型擦除的副作用,所有 通用类型共享的实例化 相同的运行时表示, 即相应的原始 类型 。换句话说,参数化 类型没有类型表示 他们自己的。因此,有 形成类文字没有意义 比如List&lt;String&gt;.classList&lt;Long&gt;.classList&lt;?&gt;.class ,因为不存在这样的 Class 对象。 只有原始类型 ListClass 表示其运行时的对象 类型。它被称为 List.class.

    【讨论】:

    • List&lt;Integer&gt; list1 = new ArrayList&lt;Integer&gt;(); List&lt;String&gt; list2 = (List&lt;String&gt;)list1; list2.add("foo"); // perfectly legal 在 Java 中你不能这样做,你会得到类型不匹配的编译错误!
    • 那么...如果我需要一个怎么办?
    • 你总是可以通过List&lt;String&gt; list2 = (List&lt;String&gt;) (Object) list1;欺骗编译器
    • 对我来说又是一个“它只适用于 C#,但不适用于 Java”。我正在反序列化一个 JSON 对象,并且 typeof(List) 在 C# 中工作得非常好,但 List.class 在 Java 中是一个语法错误。是的,正如 Cletus 写的那样,这有一个合乎逻辑的解释,但我总是想知道为什么所有这些东西都只能在 C# 中工作。
    • 完全合法是什么意思?那部分代码不编译?
    【解决方案2】:

    参数化类型没有 Class 字面量,但是有正确定义这些类型的 Type 对象。

    参见 java.lang.reflect.ParameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

    Google 的 Gson 库定义了一个 TypeToken 类,该类允许简单地生成参数化类型,并使用它以通用友好的方式指定具有复杂参数化类型的 json 对象。在您的示例中,您将使用:

    Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()
    

    我打算发布 TypeToken 和 Gson 类 javadoc 的链接,但由于我是新用户,Stack Overflow 不允许我发布多个链接,您可以使用 Google 搜索轻松找到它们

    【讨论】:

    • 有了这个,我能够创建一个具有通用 E 的类,然后使用 clzz = new TypeToken&lt;E&gt;(){}.getRawType(); 稍后使用 clzz.getEnumConstants() 迭代类似结构的枚举,然后最后使用反射调用成员方法,例如 @987654325 @这么多的胜利!谢谢!
    【解决方案3】:

    你可以通过双重转换来管理它:

    @SuppressWarnings("unchecked") Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class

    【讨论】:

    • 通过将第二个转换从Object 更改为Class,您可能可以节省(无意义的)检查运行时转换的开销。
    • @Clashsoft 使用Class 而不是Object,正如你所建议的,似乎更有意义,但它并没有消除@SuppressWarnings("unchecked") 注释的需要,它甚至添加了一个新警告:@ 987654327@
    • 你可以使用Class&lt;?&gt;:(Class&lt;List&lt;Foo&gt;&gt;)(Class&lt;?&gt;)List.class
    • @Devstr 我看到你是对的,当我尝试这样做时......使用 (Object) 或 (Class>) 的参数是什么?
    • 这个答案完全没有意义。 OP 想要参数化类路径的原因是因为他收到了unchecked 警告。这个答案不会改变/改善任何这些。 OP 甚至在他的问题中表示他不想使用 SuppressWarnings...
    【解决方案4】:

    为了解释 cletus 的回答,在运行时所有泛型类型的记录都被删除。泛型仅在编译器中处理,用于提供额外的类型安全。它们实际上只是简写,允许编译器在适当的位置插入类型转换。例如,以前您必须执行以下操作:

    List x = new ArrayList();
    x.add(new SomeClass());
    Iterator i = x.iterator();
    SomeClass z = (SomeClass) i.next();
    

    变成

    List<SomeClass> x = new ArrayList<SomeClass>();
    x.add(new SomeClass());
    Iterator<SomeClass> i = x.iterator();
    SomeClass z = i.next();
    

    这允许编译器在编译时检查您的代码,但在运行时它仍然看起来像第一个示例。

    【讨论】:

    • 感谢您的额外解释——我对泛型的理解更加清晰,现在我意识到它们不是运行时机制。 :)
    • 在我看来,这只是意味着 Sun 以平庸的方式实现了泛型,希望 Oracle 有一天能解决这个问题。 C# 的泛型实现要好得多(Anders 是神一样)
    • @MarcelValdezOrozco AFAIK,在 Java 中他们以这种方式实现它,因为他们希望旧代码(1.5 之前)在新的 JVM 上运行而不会出现任何问题。似乎这是一个非常聪明的设计决策,它关心兼容性。我不认为这有什么平庸的。
    【解决方案5】:

    您可以使用辅助方法来消除整个类中的@SuppressWarnings("unchecked")

    @SuppressWarnings("unchecked")
    private static <T> Class<T> generify(Class<?> cls) {
        return (Class<T>)cls;
    }
    

    那么你可以写

    Class<List<Foo>> cls = generify(List.class);
    

    其他用法示例是

      Class<Map<String, Integer>> cls;
    
      cls = generify(Map.class);
    
      cls = TheClass.<Map<String, Integer>>generify(Map.class);
    
      funWithTypeParam(generify(Map.class));
    
    public void funWithTypeParam(Class<Map<String, Integer>> cls) {
    }
    

    但是,由于它很少真正有用,并且该方法的使用会破坏编译器的类型检查,因此我不建议在可公开访问的地方实现它。

    【讨论】:

    • 这对于使用诸如集合之类的泛型类型实现com.fasterxml.jackson.databind.deser.std.StdDeserializer 的具体子类非常有用。
    【解决方案6】:

    Java Generics FAQ 和 cletus 的answer 听起来好像没有Class&lt;List&lt;T&gt;&gt; 的意义,但真正的问题是这非常危险:

    @SuppressWarnings("unchecked")
    Class<List<String>> stringListClass = (Class<List<String>>) (Class<?>) List.class;
    
    List<Integer> intList = new ArrayList<>();
    intList.add(1);
    List<String> stringList = stringListClass.cast(intList);
    // Surprise!
    String firstElement = stringList.get(0);
    

    cast() 看起来好像很安全,但实际上它根本不安全。


    虽然我不知道哪里不能有List&lt;?&gt;.class = Class&lt;List&lt;?&gt;&gt;,因为当您有一个基于Class 参数的泛型类型确定类型的方法时,这将非常有用。

    对于getClass(),有JDK-6184881 请求切换到使用通配符,但是看起来不会(很快)执行此更改,因为它与以前的代码不兼容(请参阅this comment)。

    【讨论】:

      【解决方案7】:

      众所周知,它会被删除。但是在类层次结构中明确提到类型的某些情况下可以知道:

      import java.lang.reflect.*;
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.LinkedHashMap;
      import java.util.Map;
      import java.util.stream.Collectors;
      
      public abstract class CaptureType<T> {
          /**
           * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type.
           *
           * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType}
           */
          public Type getTypeParam() {
              Class<?> bottom = getClass();
              Map<TypeVariable<?>, Type> reifyMap = new LinkedHashMap<>();
      
              for (; ; ) {
                  Type genericSuper = bottom.getGenericSuperclass();
                  if (!(genericSuper instanceof Class)) {
                      ParameterizedType generic = (ParameterizedType) genericSuper;
                      Class<?> actualClaz = (Class<?>) generic.getRawType();
                      TypeVariable<? extends Class<?>>[] typeParameters = actualClaz.getTypeParameters();
                      Type[] reified = generic.getActualTypeArguments();
                      assert (typeParameters.length != 0);
                      for (int i = 0; i < typeParameters.length; i++) {
                          reifyMap.put(typeParameters[i], reified[i]);
                      }
                  }
      
                  if (bottom.getSuperclass().equals(CaptureType.class)) {
                      bottom = bottom.getSuperclass();
                      break;
                  }
                  bottom = bottom.getSuperclass();
              }
      
              TypeVariable<?> var = bottom.getTypeParameters()[0];
              while (true) {
                  Type type = reifyMap.get(var);
                  if (type instanceof TypeVariable) {
                      var = (TypeVariable<?>) type;
                  } else {
                      return type;
                  }
              }
          }
      
          /**
           * Returns the raw type of the generic type.
           * <p>For example in case of {@code CaptureType<String>}, it would return {@code Class<String>}</p>
           * For more comprehensive examples, go through javadocs of {@link com.types.CaptureType}
           *
           * @return Class object
           * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
           * @see com.types.CaptureType
           */
          public Class<T> getRawType() {
              Type typeParam = getTypeParam();
              if (typeParam != null)
                  return getClass(typeParam);
              else throw new RuntimeException("Could not obtain type information");
          }
      
      
          /**
           * Gets the {@link java.lang.Class} object of the argument type.
           * <p>If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}</p>
           *
           * @param type The type
           * @param <A>  type of class object expected
           * @return The Class<A> object of the type
           * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object
           */
          public static <A> Class<A> getClass(Type type) {
              if (type instanceof GenericArrayType) {
                  Type componentType = ((GenericArrayType) type).getGenericComponentType();
                  Class<?> componentClass = getClass(componentType);
                  if (componentClass != null) {
                      return (Class<A>) Array.newInstance(componentClass, 0).getClass();
                  } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
              } else if (type instanceof Class) {
                  Class claz = (Class) type;
                  return claz;
              } else if (type instanceof ParameterizedType) {
                  return getClass(((ParameterizedType) type).getRawType());
              } else if (type instanceof TypeVariable) {
                  throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection");
              } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
          }
      
          /**
           * This method is the preferred method of usage in case of complex generic types.
           * <p>It returns {@link com.types.TypeADT} object which contains nested information of the type parameters</p>
           *
           * @return TypeADT object
           * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
           */
          public TypeADT getParamADT() {
              return recursiveADT(getTypeParam());
          }
      
          private TypeADT recursiveADT(Type type) {
              if (type instanceof Class) {
                  return new TypeADT((Class<?>) type, null);
              } else if (type instanceof ParameterizedType) {
                  ArrayList<TypeADT> generic = new ArrayList<>();
                  ParameterizedType type1 = (ParameterizedType) type;
                  return new TypeADT((Class<?>) type1.getRawType(),
                          Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList()));
              } else throw new UnsupportedOperationException();
          }
      
      }
      
      public class TypeADT {
          private final Class<?> reify;
          private final List<TypeADT> parametrized;
      
          TypeADT(Class<?> reify, List<TypeADT> parametrized) {
              this.reify = reify;
              this.parametrized = parametrized;
          }
      
          public Class<?> getRawType() {
              return reify;
          }
      
          public List<TypeADT> getParameters() {
              return parametrized;
          }
      }
      

      现在您可以执行以下操作:

      static void test1() {
              CaptureType<String> t1 = new CaptureType<String>() {
              };
              equals(t1.getRawType(), String.class);
          }
      
          static void test2() {
              CaptureType<List<String>> t1 = new CaptureType<List<String>>() {
              };
              equals(t1.getRawType(), List.class);
              equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class);
          }
      
      
          private static void test3() {
                  CaptureType<List<List<String>>> t1 = new CaptureType<List<List<String>>>() {
                  };
                  equals(t1.getParamADT().getRawType(), List.class);
              equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class);
          }
      
          static class Test4 extends CaptureType<List<String>> {
          }
      
          static void test4() {
              Test4 test4 = new Test4();
              equals(test4.getParamADT().getRawType(), List.class);
          }
      
          static class PreTest5<S> extends CaptureType<Integer> {
          }
      
          static class Test5 extends PreTest5<Integer> {
          }
      
          static void test5() {
              Test5 test5 = new Test5();
              equals(test5.getTypeParam(), Integer.class);
          }
      
          static class PreTest6<S> extends CaptureType<S> {
          }
      
          static class Test6 extends PreTest6<Integer> {
          }
      
          static void test6() {
              Test6 test6 = new Test6();
              equals(test6.getTypeParam(), Integer.class);
          }
      
      
      
          class X<T> extends CaptureType<T> {
          }
      
          class Y<A, B> extends X<B> {
          }
      
          class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {
          }
      
          void test7(){
              Z<String> z = new Z<>();
              TypeADT param = z.getParamADT();
              equals(param.getRawType(), Map.class);
              List<TypeADT> parameters = param.getParameters();
              equals(parameters.get(0).getRawType(), Integer.class);
              equals(parameters.get(1).getRawType(), List.class);
              equals(parameters.get(1).getParameters().get(0).getRawType(), List.class);
              equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class);
              equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class);
          }
      
      
      
      
          static void test8() throws IllegalAccessException, InstantiationException {
              CaptureType<int[]> type = new CaptureType<int[]>() {
              };
              equals(type.getRawType(), int[].class);
          }
      
          static void test9(){
              CaptureType<String[]> type = new CaptureType<String[]>() {
              };
              equals(type.getRawType(), String[].class);
          }
      
          static class SomeClass<T> extends CaptureType<T>{}
          static void test10(){
              SomeClass<String> claz = new SomeClass<>();
              try{
                  claz.getRawType();
                  throw new RuntimeException("Shouldnt come here");
              }catch (RuntimeException ex){
      
              }
          }
      
          static void equals(Object a, Object b) {
              if (!a.equals(b)) {
                  throw new RuntimeException("Test failed. " + a + " != " + b);
              }
          }
      

      更多信息here。但同样,几乎不可能检索到:

      class SomeClass<T> extends CaptureType<T>{}
      SomeClass<String> claz = new SomeClass<>();
      

      它被删除的地方。

      【讨论】:

      【解决方案8】:

      由于暴露的类文字没有泛型类型信息,我认为您应该假设不可能摆脱所有警告。在某种程度上,使用Class&lt;Something&gt; 与使用集合而不指定泛型类型相同。我能得出的最好的结果是:

      private <C extends A<C>> List<C> getList(Class<C> cls) {
          List<C> res = new ArrayList<C>();
          // "snip"... some stuff happening in here, using cls
          return res;
      }
      
      public <C extends A<C>> List<A<C>> getList() {
          return getList(A.class);
      }
      

      【讨论】:

        猜你喜欢
        • 2013-04-17
        • 2017-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-05
        相关资源
        最近更新 更多