Effective Java读书笔记 -- 第五章:泛型

    Java1.5发行版中增加了泛型。


第二十三条:请不要在代码中使用原生态类型

    声明中具有一个或者多个类型参数的类或者接口,就是泛型类或者接口。泛型类和接口统称为泛型。

    每种泛型定义一组参数化的类型,构成格式为:先是类或者接口的名称,接着用尖括号(<>)把对应于泛型形式类型参数的实际类型参数列表括起来。例如,List<String>。

    每个泛型有定义一个原生态类型,即不带任何实际参数的泛型名称。例如,与List<E>相对应的原生态类型是List。

    如果要使用泛型,但不确定或者不关心实际的类型参数,可以使用一个问号代替。


第二十四条:消除非受检警告

    用泛型编程时,会遇到许多编译器警告:非受检强制转化警告、非受检方法调用警告、非受检普通数组创建警告,以及非受检转换警告。

    有许多非受检警告很容易消除;有些警告比较难以消除。如果无法消除警告,同时可以证明引起警告的代码是类型安全的,可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告。

    @SuppressWarnings注解可以用在任何粒度的级别中,从单独的局部变量声明到整个类都可以。应该始终在尽可能小的范围中使用@SuppressWarnings注解。

    每当使用@SuppressWarnings注解时,都要添加一条注释,说明为什么这么做是安全的。


第二十五条:列表优先于数组

    数组与泛型相比,有两个重要的不同点。

    首先数组是协变的。如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型。而泛型是不可变的。

    如下面这段代码是合法的:

Object[] objects = new Integer[4];
objects[0] = "I don't fit it";

    而下面这段代码是不合法的:

    

    

    其实无论哪种方法,都不能将String类型放在Integer容器中,但是利用数组,只能在运行时发现错误;利用列表,则可以在编译时发现错误。

    数组与泛型之间的第二大区别在于,数组是具体化的,因此数组会在运行时才知道并检查它们的元素类型约束。而泛型则是通过擦除来实现的。因此泛型只在编译时强化它们的类型信息,并在运行时丢弃它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用(见第二十三条)。

    由于上述的区别,因此数组和泛型不能很好地混合使用。数组是斜边且可以具体化的;泛型是可变的且可以被擦除的。


第二十六条:优先考虑泛型

    一般来说,将集合声明参数化,以及使用JDK所提供的泛型和泛型方法,这些都不太困难。编写自己的泛型会比较困难一些,但值得花时间去学习如何编写。

    将之间编写过的Stack改写成泛型的:

package JavaDay5_29;

import java.util.Arrays;
import java.util.EmptyStackException;

/**
 * @author [email protected]
 * @date 18-5-29  下午8:27
 */

public class Stack<E> {
    private E[] items;
    private int size = 0;
    private static final int DEFAULT_INITAL_CAPACITY = 16;

    public Stack() {
        items = new E[DEFAULT_INITAL_CAPACITY];
    }

    public void push(E o) {
        ensureCapacity();
        items[size++] = o;
    }

    public E pop() {
        if(size == 0) {
            throw new EmptyStackException();
        }
        E result = items[--size];
        items[size] = null;//消除过期引用
        return result;
    }

    /**
     * 确保容量足够且自动扩增
     */
    private void ensureCapacity() {
        if(items.length == size) {
            items = Arrays.copyOf(items, 2 * size + 1);
        }
    }
}

    但在构造函数中会有警告。

    可将构造函数更改成:

    public Stack() {
        items = (E[]) new Object()[DEFAULT_INITAL_CAPACITY];
    }

    此时还是会有警告,但此种用法确实是合理的,可加上@SuppressWarnings("unchecked")注解即可。

    总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更加容易。


第二十七条:优先考虑泛型方法

    就如同类可以从泛型中受益一般,方法也一样。静态工具方法尤其适合泛型化。

    下面为一个泛型方法的栗子:

    public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<>();
        result.addAll(s2);
        return result;
    }

    union方法的局限性在于,三个集合的类型(两个输入参数和一个返回参数)必须全部相同。利用有限制的通配符类型,可以使这个方法变得更加灵活(见第二十八条)。

    泛型方法的一个显著特征是:无需明确指定类型参数的值,不像调用泛型构造器的时候是必须指定。

    总而言之,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来得更加安全,也更加容易。


第二十八条:利用有限制通配符来提升API的灵活性

    Set<? extends E>可以存放E和E的子类。

    Set<? super E>可以存放E和E的父类。

    使用通配符可以让API更加灵活。


第二十九条:优先考虑类型安全的异构容器

    略。(本章内容多而杂,最近时间不够,以后重读补上)


总结:本章主要讲解了泛型的知识和泛型的用法,以及如何正确高效地使用泛型。共勉。        

猜你喜欢

转载自blog.csdn.net/weixin_41704428/article/details/80500982