一 先来小菜一碟
看代码:
public static <E> void christmas(List<E> list) {
E[] snapshot = list.toArray();
for (E e : snapshot) {
//
}
}
上面这段代码是编译不通过的, 出现了error:
Error:(56, 36) java: 不兼容的类型: java.lang.Object[]无法转换为E[]
也可以:
E[] snapshot = (E[])list.toArray();
加上这样的强制转换,compiler 在compile时会提示warning: unchecked cast: 'java.lang.Object[]' to 'E[]'
表明这里是类型不安全的,这样的转换是不好的。
根据《Effective Java》中所叙述的,数组和泛型有着非常不同的类型规则,数组是协变(covariant)和具体化的(reified),泛型是不可变(invariant)且可以被擦除的(erased)。数组提供了运行时类型的安全,但是没有编译时的类型安全。数组和泛型不能很好的混合使用,如果发现将它们混合起来使用,并且得到了编译时错误或警告,就应该是用List去代替Array。
上面的代码就可以修改为:
public static <E> void christmas(List<E> list) {
List<E> snapshot = new ArrayList<>(list);
for (E e : snapshot) {
//
}
}
二 上主菜
内容都是来自《Effective Java》,记录以备后续学习。
下面的代码模拟 数据结构 栈(Stack)。
Stack 版本一
import java.util.Arrays;
import java.util.EmptyStackException;
public class StackDemo {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackDemo() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
为了让Stack泛型化并支持更多的类型,将StackDemo类中的Object替换成 形式类型参数 E,如下:
Stack版本二
import java.util.Arrays;
import java.util.EmptyStackException;
public class StackGericDemo<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackGericDemo() {
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
然而
elements = new E[DEFAULT_INITIAL_CAPACITY];
这行代码是有问题的,不能这样去创建数组,compiler报error错误。compiler表示不能证明你的程序是类型安全的。
为了抑制掉error错误, 可以加上泛型转换
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
这样compiler就只会报unchecked cast的warning了,谢天谢地,但问题还没解决。
由于E是一个不可具体化的(non-reifiable), 编译器不能确定你的程序是类型安全的,但是写代码的你可以证明啊。elements这个变量保存在一个private字段中,没有提供对应的client可以访问到elements的方法,保存到elements中的值是通过push方法加进去的,是E类型的,so compiler爆出的unchecked cast warning对这个Stack的程序是没有危害的(no harm)。
一旦你证实到这样的unchecked cast是安全的,在小局部范围使用@SuppressWarnings("unchecked")
将这个警告给抑制掉吧(作为一个有代码洁癖的你)。
新版本的Stack Java Code:
import java.util.Arrays;
import java.util.EmptyStackException;
public class StackGericDemo<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked")
public StackGericDemo() {
/**
* you can’t create an array of a non-reifiable type, such as E
* 不能创建不可具体化的数组
*/
// elements = new E[DEFAULT_INITIAL_CAPACITY];
/**
* 这是类型不安全的
* 但这里是能保证这个程序是类型安全的,因为指定了类型参数 E
* 这里的 unchecked cast can do no harm
*/
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
Stack版本三
还有一种方法是将E[]
换成Object[]
, code :
import java.util.Arrays;
import java.util.EmptyStackException;
public class StackGericPerfectDemo<E> {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackGericPerfectDemo() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
/**
* unchecked cast, 是否能转换
*
Stack.java:19: warning: [unchecked] unchecked cast
found : Object, required: E
E result = (E) elements[--size];
Because E is a non-reifiable type,
there’s no way the compiler can check the cast at runtime.
*/
// push requires elements to be of type E, so cast is correct
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
版本二和版本三相比,禁止an uncheck cast to an array type 比禁止a scalar type(标量类型)要更加危险一些,所以建议还是版本三的方案。但是Stack的实际的使用中,会在代码的多个地方从数组中读取值,选择版本三的方案的话会需要many cast to E
,版本二只需要a single cast to E[]
,由此版本二的Stack的泛型设计会更常用一点。