Effective Java笔记第四章泛型第四节优先考虑泛型

本文探讨了Effective Java第四章关于泛型的内容,解释了如何通过泛型改进Stack类以避免类型转换。文章介绍了两种解决创建泛型数组问题的方法:一是使用未受检转换,二是将元素类型改为Object并进行转换。尽管未受检转换可能产生警告,但在确保类型安全的情况下可以使用。作者建议根据个人偏好和代码中元素读取的频率选择适当的方法。泛型的使用可以提高代码的安全性和易用性,建议在设计新类型时优先考虑泛型化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Effective Java笔记第四章泛型

第四节优先考虑泛型

下面我们举个例子,将他作为泛型化的主要备选对象,换句话说,可以适当的强化这个类来利用泛型。

public class Stack {

    private Object[] element;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 1;

    public Stack() {
        element = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        element[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        Object result = element[--size];
        element[size] = null;
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (element.length == size) {
            element = Arrays.copyOf(element, 2 * size + 1);
        }
    }

}

首先我们代码进行简单的转换,先用类型参数替换所有的Object类型:

public class StackImprove1<E> {
    
    private E[] element;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 1;

    public StackImprove() {
    	//这里会爆编译错误
        element=new E[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        element[size++] = e;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        E result = element[--size];
        element[size] = null;
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (element.length == size) {
            element = Arrays.copyOf(element, 2 * size + 1);
        }
    }
    
}

以上代码是有错误的,你不可能创建不可具体化类型的数组。解决这个问题有两种方法:

1)直接绕过创建泛型数组的禁令:创建一个Object的数组,并将他转换成泛型数组类型。这样的话错误是消除了,但是产生了一条警告。表示这种用法是合法的,但是不是类型安全的。

编译器不可能证明你的程序是类型安全的,但是你可以证明。你必须确保未受检的转换不会危及到程序的类型安全性。相关的数组保存在一个私有的域中,永远不会被返回到客户端,或者传递给任何其他方法。这个数组中保存的唯一元素,是传给push方法的那些元素,他们的类型为E,因此未受检的转换不会有任何危害。

一旦你证明了未受检的转换是安全的,就要在尽可能小的范围中禁止警告。

下面我们对上面的代码进行第一种方式的改造:

public class StackImprove1<E> {
    
    private E[] element;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 1;

//    public StackImprove() {
//        element=new E[DEFAULT_INITIAL_CAPACITY];
//    }

    //The elements array will contain only E instances from push(E) 元素数组将只包含来自push(E)的E个实例
    //This is sufficient to ensure type safety ,but the runtime 这只能在运行时确保安全
    //type of the array was not be E[] ; it will always be Object[] 数组的类型不是E[];它将始终是Object[]
    @SuppressWarnings("unchecked")
    public StackImprove1() {
        element=(E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        element[size++] = e;
    }

    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        E result = element[--size];
        element[size] = null;
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (element.length == size) {
            element = Arrays.copyOf(element, 2 * size + 1);
        }
    }
    
}

2)将elements域的类型从E[ ]改为Object[ ],之后把数组中获得的元素由Object转换为E。由于E是一个不可具体化的类型,编译器无法在运行时检验转换。你还是可以自己证实未受检的转换是安全的,因此可以禁止该警告。

下面我们对上面的代码进行第二种方式的改造:

public class StackImprove2<E> {

    private Object[] element;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 1;

    public StackImprove2() {
        element = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        element[size++] = e;
    }

    //Appropriate suppression of unchecked warning 在尽可能小的范围使用禁止警告
    public E pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        //push requires elements to be of type E,so cast is correct
        @SuppressWarnings("unchecked") E result =
                (E) element[--size];
        element[size] = null;
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (element.length == size) {
            element = Arrays.copyOf(element, 2 * size + 1);
        }
    }

    public static void main(String[] args) {
        StackImprove2<String> stringStack=new StackImprove2<>();
        List<String> stringList=new ArrayList<>();
        stringList.add("cc");
        stringList.add("dd");
        for (String s : stringList) {
            stringStack.push(s);
        }
        System.out.println(stringStack.element.length);
        while (!stringStack.isEmpty()){
            System.out.println(stringStack.pop().toUpperCase());
        }
    }

}

具体选择这两种方法中的哪一种来处理泛型数组创建错误,则主要看个人偏好了。所有其他的东西都一样,但是禁止数组类型的未受检转换比标量类型的更加危险,所以建议采用第二种方法。但是在实际的泛型类中,或许代码中会有多个地方需要从数组中读取元素,因此选择第二种方案需要多次转换成E,而不是只转换成E[ ],这也是第一种方案之所以更常用的原因。

类型参数列表(E extends ?),类型参数E被称为有限制的类型参数。注意,子类型关系确定了,每个类型都是他自身的子类型。

总之,使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。在设计新类型的时候,要确保他们必须要这种转换就可以使用,这通常意味着要把类做成泛型的,只要时间允许,就把现有的类型都泛型化。这对于这些类型的新用户来说更加轻松,又不会破坏现有的客户端。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值