看《Effective Java》学会的泛型设计

来源:互联网 发布:淘宝店开多久才有生意 编辑:程序博客网 时间:2024/04/29 19:04

一 先来小菜一碟

看代码:

    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的泛型设计会更常用一点。

0 0
原创粉丝点击