将具有泛型参数的类传递给注释

时间:2018-01-19 17:16:56

标签: java generics types

我想知道为什么Java编译器不接受这个赋值:

Class<? extends List<?>> blbost = ArrayList.class;

请注意我对Class<? extends List<?>> blobst = (Class<? extends List<?>>)ArrayList.class;等解决方案不感兴趣,我想知道它可以用于什么原因。

最初的动机是在注释中使用泛型:

@SomeAnnotation(SomeFunction.class) // Syntax error here!
private static class SomeClass  {
}

private @interface SomeAnnotation {
    Class<? extends Function<?, ?>> value();
}
private static class SomeFunction<T> implements Function<T, String> {
    @Override
    public String apply(T t) {
        return t.toString();
    }
}

我不能在注释中使用强制转换,因为值必须是常量。

为什么我不能将SomeFunction作为参数传递给SomeAnnotation,我该怎么办呢?

2 个答案:

答案 0 :(得分:5)

首先,简短的回答是Class<ArrayList>不是Class<? extends List<?>>的子类型。实际上存在如下图所示的关系:

                      Class
                        |
                     Class<?>
                        |
              Class<? extends List>
                  /           \
Class<? extends List<?>>    Class<? extends ArrayList>
         |                             |
   Class<List<?>>              Class<ArrayList>

换句话说,Class<ArrayList>Class<? extends List<?>>共享一个常见的超类型。

这主要包含在规范的4.10.24.5.1部分中。

如果Class<? extends List<?>> cls = ArrayList.classClass<? extends List<?>>的超类型,Class<ArrayList>之类的作业将被编译,因此我们需要找出Class<ArrayList>的超类型是什么:

  

给定泛型类型声明C<F1,...,Fn> n &gt; 0),参数化类型C<T1,...,Tn>的直接超类型,其中Ti(1≤< em> i ≤ n )是一种类型,全部如下:

     
      
  1. D<U1 θ,...,Uk θ>,其中D<U1,...,Uk>是泛型类型,是泛型类型C<T1,...,Tn>的直接超类型,θ是替换[F1:=T1,...,Fn:=Tn]

  2.   
  3. C<S1,...,Sn>,其中Si包含Ti(1≤ i n )。

  4.   
  5. 类型Object,如果C<F1,...,Fn>是没有直接超接口的通用接口类型。

  6.   
  7. 原始类型C

  8.   

规则1基本上只是说如果你有一个类型,例如ArrayList<String>然后它有超类型List<String>Iterable<String>等,其中type参数仅替换为超类或超接口。 Class没有相关的超类型,因为对于这个特例我们并不关心它的超类或超接口。

规则2是我们在这里真正感兴趣的唯一一个,并且JLS将我们引导到4.5.1以解释何时类型参数包含另一个类型参数:

  

如果由T1表示的类型集合,则类型参数T2被称为包含另一个类型参数T2 <= T1,写为T2可证明是T1表示的类型集的子集,在以下规则的反身和传递闭包下(其中<:表示子类型):

     
      
  • ? extends T <= ? extends S if T <: S

  •   
  • [...]

  •   
  • T <= ? extends T

  •   
  • [...]

  •   

使用这两个规则和传递属性,如果? extends ST的超类型,我们可以推导出类型参数S包含类型参数T的规则。

使用此规则,我们现在可以按如下方式构建原始问题:

    如果Class<? extends List<?>>包含Class<ArrayList>,则
  1. ? extends List<?>ArrayList的超类型。

  2. 如果? extends List<?>ArrayList的超类型,则
  3. List<?>包含ArrayList

  4. 那么原始类型ArrayList的超类型是什么?我们可以参考4.10.2:

      

    给定泛型类型声明C<F1,...,Fn> n &gt; 0),原始类型C的直接超类型是以下所有:

         
        
    1. 原始类型C的直接超类。

    2.   
    3. 原始类型C的直接超接口。

    4.   
    5. 类型Object,如果C<F1,...,Fn>是没有直接超接口的通用接口类型。

    6.   

    (规则3与我们在此考虑的例子无关。)

    另外,我们需要快速跳转到4.8

      

    原始类型的超类(分别是超级接口)是泛型类型的任何参数化的超类(超接口)的擦除。

    换句话说,ArrayList的超类型是所有原始类型AbstractListListIterable等。

    通过这一切,我们最终可以得出结论:Class<ArrayList>不是Class<? extends List<?>>的子类型,这就是为什么问题中的赋值不能编译的原因。

    对于不涉及投射的解决方案,Marv展示了其中一个,即将Class<? extends Function<?, ?>>更改为Class<? extends Function>

    另一个是永远不会使用,例如SomeFunction.class,其原始类型参数为Class<SomeFunction>。相反,您始终使用具体类型参数扩展SomeFunction。例如,如果您想要SomeFunction<Double>,则需要创建一个显式子类:

    class DoubleFunction extends SomeFunction<Double> {}
    

    然后您就可以使用DoubleFunction.class

答案 1 :(得分:1)

ArrayList不是List<?>的子类型的原因是因为Java中的泛型是invariant,这意味着对于任何两种不同的类型Type1和{{1} },Type2既不是List<Type1>的子类型也不是超类型。

这也扩展到原始类型和参数化类型,这意味着像List<Type2>这样的原始类型永远不能是ArrayList等参数化类型的子类型或超类型,并且可能更直观地参数化类似List<Type1>的类型永远不会是ArrayList<Type1>等原始类型的子类型,即使ListArrayList的子类型。

从内部类型中删除通配符将允许您将原始类型类传递给注释:

List

不是也不可能从像import java.util.function.Function; public class Example { @SomeAnnotation(SomeFunction.class) private static class SomeClass { } private @interface SomeAnnotation { Class<? extends Function> value(); } private static class SomeFunction<T> implements Function<T, String> { @Override public String apply(T t) { return t.toString(); } } } 这样的参数化类型中获取类文字。对于任何类型List<String>Type将是语法错误,因此在此处使用原始类型是允许的,即使通常您不应使用原始类型。