【问题标题】:Why can't I create an array of a generic type?为什么我不能创建一个泛型类型的数组?
【发布时间】:2012-07-23 21:09:36
【问题描述】:

简而言之,这不会编译:

public <A> void test() {
    A[] temp = new A[]{};
}

是因为向后兼容性的问题,还是语言设计中的一些基本因素阻止了它?

【问题讨论】:

  • “是不是语言设计中的一些基本因素阻止了它?” - 显然,是的。

标签: java language-design language-details


【解决方案1】:

底线是表示数组的类必须知道组件类型。因此 Class 对象上的方法:

public Class<?> getComponentType()
Returns the Class representing the component type of an array. If this class does not represent an array class this method returns null.

所以你什么时候会尝试:

 A[] a = new A[0];

在编译时,很明显我们不知道类型,因为它是一个泛型参数。在运行时,由于类型擦除,我们不知道类型。所以实例化数组是不可能的。

认为上面的语句等价于:

 A[] a = (A[])Array.newInstance(???, 0);

并且由于类型擦除,我们无法在运行时获取 A 的类。

有人问为什么不让编译器简化为 Object[] 或 Number[] 或类似的东西?

这是因为会根据组件类型返回不同的 Class。所以:

 new Object[0].getClass() 
 new Integer[0].getClass()

不是同一个类。特别是类上的“getComponentType()”方法会返回不同的值。

因此,如果您将其简化为 Object[] 而不是 A[],您实际上并没有返回 A[] 类型的东西,而是返回了 Object[]。 Object[] 不能大小写为 Integer[] 并且会产生 ClassCastException。

【讨论】:

  • 类型参数&lt;A&gt;Object[] a在字节码中的A[] a有区别还是都编译成相同的字节码?
  • 不一样,因为结果调用 a.getClass() 会产生不同的 Class 实例。我会编辑解释
  • 我得到了很多好的答案,我都投了赞成票,但我认为这个是最干净的深度答案。
【解决方案2】:

键入erasure 是您要查找的单词。这基本上意味着generic 信息在编译时被删除。造成这种情况的主要原因是向后兼容性。旧程序仍应在新的 java 虚拟机上运行。

【讨论】:

    【解决方案3】:

    在 Java 中,数组和泛型的类型系统是不兼容的。 存在两个主要差异领域:动态与静态类型检查协方差

    泛型是静态检查的:也就是说,编译器确保类型定义是一致的。 “类型擦除”是一种妥协,以确保 JVM 中的向后兼容性。编译后,泛型类型定义不再可用。例如。 List&lt;T&gt; 变为 List

    相比之下,数组是动态类型检查的。考虑以下示例:

    String strings[] = {"a","b","c"};
    Object simple[] = strings;
    simple[0] = new Object(); // Runtime error -> java.lang.ArrayStoreException 
    

    协变是容器基于内容的继承关系。 给定类型 A、B 和容器 C,如果 B isAssignableFrom(A) => C isAssignable C。

    Java 中的数组是协变的,在前面的示例中,鉴于 Class&lt;Object&gt;.isAssignableFrom(Class&lt;String&gt;) =&gt; Object[] is assignable from String[]

    相比之下,泛型类型在 Java 中不是协变的。使用相同的示例:

    List<String> stringList = new ArrayList<String>();
    List<Object> objectList = stringList; // compiler error - this is not allowed.
    

    鉴于泛型实现的类型擦除,类型信息会在转换中丢失,因此如果您可以创建泛型类型的数组,动态类型检查将受到影响。

    进一步了解这些问题的复杂性和影响:Arrays in Java Generics

    【讨论】:

      【解决方案4】:

      这与new A() 无法工作的原因相同(或几乎相同):您期望编译器知道A 的运行时类型,而它显然无法知道。如果 Java 泛型类似于 C++ 模板,这工作,其中为涉及A 的模板的每个实例化生成新代码。

      【讨论】:

      • new A()有什么相似之处?你能详细说明一下吗?
      • @aioobe A[] 是一个独特的类型,就像 A 本身一样。它的一个实例知道它的类型。这与List&lt;A&gt; 非常不同,后者在运行时只是一个List
      • 当然,但new A[] {} 不涉及创建任何对象。我不清楚与new A() 的联系。
      • @aioobe 你怎么看出来的?在 Java 中没有出现new 涉及创建对象。 new A[0] 创建一个 A[] 类型的新对象。
      • 不同之处在于new A() 需要默认构造函数,而new A[]{} 不需要。所以我觉得不一样。
      【解决方案5】:

      是的,有一个根本原因,归结为type erasure

      考虑以下 sn-p:

      A[] temp = new A[5];       // Assume this would compile.
      
      Object[] objects = temp;   // This is allowed, since A extends Object.
      
      objects[0] = new String(); // This does *not* throw an ArrayStoreException
                                 // due to type erasure since the original type of A
                                 // is now Object.
      
      A t = temp[0];             // Now this seemingly innocent line would *fail*.
      

      相关问题:

      【讨论】:

      • List&lt;Foo&gt; fooList; 会遇到同样的问题,然后将 fooList.toArray(new Foo[fooList.size()]) 传递给 public &lt;A&gt; void test(A[] as);Generics 和数组不适合,因为数组不是类型安全的。
      猜你喜欢
      • 2020-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多