Zhu*_* Li 17 java arrays generics
好吧,我已经阅读了很多这个问题的答案,但我有一个更具体的问题.以下面的代码片段为例.
public class GenericArray<E>{
E[] s= new E[5];
}
Run Code Online (Sandbox Code Playgroud)
在类型擦除之后,它变成了
public class GenericArray{
Object[] s= new Object[5];
}
Run Code Online (Sandbox Code Playgroud)
这段代码似乎运作良好.为什么会导致编译时错误?
此外,我从其他答案中得知,以下代码适用于同一目的.
public class GenericArray<E>{
E[] s= (E[])new Object[5];
}
Run Code Online (Sandbox Code Playgroud)
我读过一些评论说上面的代码是不安全的,但为什么它不安全?任何人都可以向我提供一个特定的例子,其中上面的代码导致错误?
另外,以下代码也是错误的.但为什么?擦除后似乎也能正常工作.
public class GenericArray<E>{
E s= new E();
}
Run Code Online (Sandbox Code Playgroud)
从文档中:您可以放置在数组上的唯一类型是可以重新生成的类型,即:
它引用非泛型类或接口类型声明.
它是一种参数化类型,其中所有类型参数都是无界通配符(第4.5.1节).
它是原始类型(§4.8).
它是一种原始类型(§4.2).
它是一种数组类型(第10.1节),其元素类型是可恢复的.
它是一种嵌套类型,其中,对于由"."分隔的每个类型T,T本身是可再生的.
这意味着"通用"数组的唯一合法声明就是这样List<?>[] elements = new ArrayList[10];.但这绝对不是通用数组,它是一个List未知类型的数组.
Java抱怨你执行强制转换E[]的主要原因是因为它是一个未经检查的强制转换.也就是说,您将从已检查的类型明确地转到未经检查的类型; 在这种情况下,检查的泛型类型E为未选中的类型Object.但是,这是创建通用数组的唯一方法,如果必须使用数组,通常认为是安全的.
通常,避免这种情况的建议是在可能的地方和时间使用通用集合.
这段代码似乎运行良好。为什么会导致编译时错误?
首先,因为它会违反类型安全(即它是不安全的- 见下文),并且一般来说,可以静态确定执行此操作的代码不允许编译。
请记住,由于类型擦除,类型E在运行时是未知的。该表达式new E[10]最多可以创建一个已擦除类型的数组,在本例中Object,呈现原始语句:
E[] s= new E[5];
Run Code Online (Sandbox Code Playgroud)
相当于:
E[] s= new Object[5];
Run Code Online (Sandbox Code Playgroud)
这当然是不合法的。例如:
String[] s = new Object[10];
Run Code Online (Sandbox Code Playgroud)
...出于基本相同的原因,不可编译。
您认为删除后,该声明将是合法的,这意味着您认为这意味着原始声明也应被视为合法。然而这是不对的,可以通过另一个简单的例子来证明:
ArrayList<String> l = new ArrayList<Object>();
Run Code Online (Sandbox Code Playgroud)
删除上述内容将是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)。
谁能给我提供一个具体的例子,其中上面的代码会导致错误?
本质问题是可以将非E对象放入通过执行强制转换创建的数组中(如 中所示(E[]) new Object[5])。例如,假设有一个foo带有Object[]参数的方法,定义为:
void foo(Object [] oa) {
oa[0] = new Object();
}
Run Code Online (Sandbox Code Playgroud)
然后采取以下代码:
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...)
Run Code Online (Sandbox Code Playgroud)
String即使在调用foo. 另一方面:
E[] ea = (E[]) new Object[5];
foo(ea);
E e = ea[0]; // e may now refer to a non-E object!
Run Code Online (Sandbox Code Playgroud)
该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
}
}
Run Code Online (Sandbox Code Playgroud)
该代码在运行时会生成 ClassCastException,并且不安全。另一方面,没有不安全操作(例如强制转换)的代码不会产生此类错误。
另外,下面的代码也是错误的。但为什么?删除后似乎效果也很好。
有问题的代码:
public class GenericArray<E>{
E s= new E();
}
Run Code Online (Sandbox Code Playgroud)
擦除后,这将是:
Object s = new Object();
Run Code Online (Sandbox Code Playgroud)
虽然这一行本身没问题,但将这些行视为相同会引入我上面描述的语义更改和安全问题,这就是编译器不会接受它的原因。举例说明为什么它可能会导致问题:
public <E> E getAnE() {
return new E();
}
Run Code Online (Sandbox Code Playgroud)
...因为类型擦除后,“new E()”将变成“new Object()”,并且E从方法返回非对象显然违反了其类型约束(它应该返回 an E),因此是不安全的。如果上述方法可以编译,并且您使用以下命令调用它:
String s = <String>getAnE();
Run Code Online (Sandbox Code Playgroud)
...那么您将在运行时收到类型错误,因为您将尝试将 an 分配Object给String变量。
进一步说明/澄清:
ClassCastException或多个异常,但这些异常仅发生在明确定义的点。ArrayStoreException也就是说,通常只有ClassCastException在执行强制转换时才能获得 a,该操作本身就存在这种风险。ArrayStoreException同样,只有将值存储到数组中时才能获得 an 。new E()可能期望生成实际类型参数的实例,而实际上它只能生成已擦除类型的实例。允许它编译是不安全的并且可能会造成混乱。一般来说,您可以使用E它来代替实际类型,而不会产生任何不良影响,但实例化的情况并非如此。