好吧,我已经阅读了很多这个问题的答案,但我有一个更具体的问题。以下面的代码片段为例。
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();
}
答案 0 :(得分:7)
Array declarations are required to have a reifiable type和generics 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
变量。
进一步说明/澄清:
ClassCastException
或ArrayStoreException
或其他例外。代码,但这些异常只发生在明确定义的点上。也就是说,在执行强制转换时,通常只能获得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。