为什么我不能在Java中创建类型参数的数组?

时间:2015-05-06 11:04:19

标签: java arrays generics

好吧,我已经阅读了很多这个问题的答案,但我有一个更具体的问题。以下面的代码片段为例。

public class GenericArray<E>{
    E[] s= new E[5];
}

在类型擦除后,它变为

public class GenericArray{
    Object[] s= new Object[5];
}

这段代码似乎运作良好。为什么会导致编译时错误?

此外,我从其他答案中了解到,以下代码可用于同一目的。

public class GenericArray<E>{
    E[] s= (E[])new Object[5];
}

我读过一些评论说上面的代码是不安全的,但为什么它不安全?任何人都可以提供一个特定的例子,上面的代码会导致错误吗?

此外,以下代码也是错误的。但为什么?擦除后似乎也能正常工作。

public class GenericArray<E>{
        E s= new E();
    }

4 个答案:

答案 0 :(得分:7)

Array declarations are required to have a reifiable typegenerics are not reifiable.

从文档中:您可以在阵列上放置的唯一类型是可以回复的类型,即:

  
      
  • 它指的是非泛型类或接口类型声明。

  •   
  • 这是一个参数化类型,其中所有类型参数都是无界通配符(§4.5.1)。

  •   
  • 这是一种原始类型(§4.8)。

  •   
  • 这是一种原始类型(§4.2)。

  •   
  • 这是一个数组类型(第10.1节),其元素类型是可以恢复的。

  •   
  • 这是一种嵌套类型,对于每个以“。”分隔的T类型,T本身都是可以恢复的。

  •   

这意味着“通用”数组的唯一合法声明类似于List<?>[] elements = new ArrayList[10];。但这肯定是不是一个通用数组,它是一个未知类型的List数组。

Java抱怨您对E[]执行投射的主要原因是因为它是unchecked cast。也就是说,您将从已检查的类型明确地转到未经检查的类型;在这种情况下,检查的泛型类型E为未选中的类型Object。但是,这是创建通用数组的唯一方法,如果必须使用数组,通常认为是安全的。

一般而言,避免这种情况的建议是在可能的地方和时间使用通用集合。

答案 1 :(得分:3)

  

这段代码似乎运作良好。为什么会导致编译时错误?

首先,因为它会违反类型安全性(即不安全 - 见下文),并且通常可以静态确定执行此操作的代码不允许编译。

请记住,由于类型擦除,在运行时不知道类型E。表达式new E[10]最多可以创建一个已擦除类型的数组,在本例中为Object,呈现原始语句:

E[] s= new E[5];

相当于:

E[] s= new Object[5];    

这当然不合法。例如:

String[] s = new Object[10];
由于基本相同的原因,

......不可编辑。

您认为在删除后,该陈述是合法的,暗示您认为这意味着原始陈述也应被视为合法。然而,这是不对的,正如另一个简单的例子所示:

ArrayList<String> l = new ArrayList<Object>();

以上的删除是ArrayList l = new ArrayList();,这是合法的,而原文显然不是。

从更哲学的角度来看,类型擦除不应该​​改变代码的语义,但在这种情况下它会这样做 - 创建的数组将是Object的数组而不是数组E(无论E可能是什么)。然后可以在其中存储非E对象引用,而如果数组实际上是E[],则应该生成ArrayStoreException

  

为什么不安全?

(请注意,我们现在正在谈论E[] s= new E[5];E[] s = (E[]) new Object[5];取代的情况

它是不安全的(在本例中是 type unsafe 的缩写),因为它在运行时创建了一个变量(s)保存对象引用的情况实例不是变量的声明类型的子类型(Object[]不是E[]的子类型,除非E == Object)。< / p>

  

任何人都可以向我提供一个具体示例,其中上面的代码会导致错误吗?

基本问题是可以将非E对象放入通过执行转换创建的数组中(如(E[]) new Object[5]中所示)。例如,假设方法foo采用Object[]参数,定义为:

void foo(Object [] oa) {
    oa[0] = new Object();
}

然后使用以下代码:

String [] sa = new String[5];
foo(sa);
String s = sa[0]; // If this line was reached, s would
                  // definitely refer to a String (though
                  // with the given definition of foo, this
                  // line won't be reached...)

即使在调用String之后,数组肯定包含foo个对象。另一方面:

E[] ea = (E[]) new Object[5];
foo(ea);
E e = ea[0];  // e may now refer to a non-E object!

foo方法可能已将非E对象插入到数组中。因此,即使第三条线看起来安全,第一条(不安全)线也违反了保证安全的限制。

一个完整的例子:

class Foo<E>
{
    void foo(Object [] oa) {
        oa[0] = new Object();
    }

    public E get() {
        E[] ea = (E[]) new Object[5];
        foo(ea);
        return ea[0];  // returns the wrong type
    }
}

class Other
{
    public void callMe() {
        Foo<String> f = new Foo<>();
        String s = f.get();   // ClassCastException on *this* line
    }
}

代码在运行时会生成ClassCastException,并且不安全。另一方面,没有不安全操作的代码(如强制转换)不会产生此类错误。

  

此外,以下代码也是错误的。但为什么?擦除后似乎也能正常工作。

有问题的代码:

public class GenericArray<E>{
    E s= new E();
}

擦除后,这将是:

Object s = new Object();

虽然这条线本身很好,但将线条视为相同会引入我上面描述的语义变化和安全问题,这就是编译器不能接受它的原因。作为它可能导致问题的原因的一个例子:

public <E> E getAnE() {
    return new E();
}

...因为在类型擦除之后,新的E()&#39;将成为新的Object()&#39;从该方法返回一个非E对象显然违反了它的类型约束(它应该返回E),因此不安全。如果上面的方法是编译的,你用:

调用它
String s = <String>getAnE();

...然后您会在运行时收到类型错误,因为您会尝试将Object分配给String变量。

进一步说明/澄清:

  • 不安全(这是&#34的缩写;类型不安全&#34;)意味着它可能会导致代码中的运行时类型错误,否则会发出声音。 (它实际上意味着更多,但这个定义足以满足这个答案的目的。)
  • 可以使用&#34; safe&#34;导致ClassCastExceptionArrayStoreException或其他例外。代码,但这些异常只发生在明确定义的点上。也就是说,在执行强制转换时,通常只能获得ClassCastException,这种操作本身就存在这种风险。同样,在将值存储到数组中时,只能获得ArrayStoreException
  • 在抱怨操作不安全之前,编译器不会验证是否会发生此类错误。它只是知道某些操作可能能够导致问题,并警告这些情况。
  • 您无法创建类型参数的新实例(或数组)既是旨在保护安全的语言功能,也可能是为了反映由...构成的实现限制使用类型擦除。也就是说,new E()可能会产生实际类型参数的实例,而实际上它只能生成擦除类型的实例。允许它编译将是不安全的并且可能令人困惑。通常,您可以使用E代替实际类型而不会产生不良影响,但实例化不是这种情况。

答案 2 :(得分:2)

编译器可以使用Object类型的变量来执行Cat类型的变量可以执行的任何操作。编译器可能必须添加类型转换,但是这样的类型转换将抛出异常或产生对Cat实例的引用。因此,SomeCollection<T>生成的代码不必实际使用T类型的任何变量;编译器可以用T替换Object,并在必要时将函数返回值等函数转换为T

然而,编译器无法使用Object[]来执行Cat[]可以执行的所有操作。如果SomeCollection[]具有类型为T[]的数组,则在不知道T的类型的情况下,将无法创建该数组类型的实例。它可以创建Object[]的实例,并在不知道T类型的情况下存储对T实例的引用,但任何将此类数组转换为T[]的尝试都会保证失败,除非T恰好是Object

答案 3 :(得分:0)

假设Java中允许使用通用数组。现在,看看下面的代码,

Object[] myStrs = new Object[2];
myStrs[0] = 100;  // This is fine
myStrs[1] = "hi"; // Ambiguity! Hence Error.

如果允许用户创建通用数组,那么用户可以按照我在上面的代码中所示进行操作,这会使编译器感到困惑。它违背了数组的目的(Arrays can handle only same/similar/homogeneous type of elements,还记得吗?)。如果你想要异构数组,你总是可以使用class / struct数组。

更多信息here