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

一 先来小菜一碟

看代码:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值