第26条:优先考虑泛型

public class Stack {

    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITAL_CAPACITY = 16;
   
    public Stack() {
        elements = new Object[DEFAULT_INITAL_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;
        return result;
    }
   
    public boolean isEmpty() {
        return size == 0;
    }

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

}

这个类是泛型化的主要备选对象,换句话说,可以适当的强化这个类来利用泛型。根据实际情况来看,必须转换从堆栈中弹出的对象,以及可能在运行时失败的那些转换。将类泛型化的第一个步骤,就是给他的声明添加一个或者多个类型参数。在这个例子中有一个类型参数,它表示堆栈的元素类型,这个参数的名称通常为E(将这个类强化为带一个或多个泛型参数的类)

下一步是用相应的类型参数替换所有的Object类型,然后试着编译最终的程序

public class StaticGerneric<E> {
    public E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITAIL_CAPACITY = 3;

    public StaticGerneric() {
        elements = new E[DEFAULT_INITAIL_CAPACITY];//报错Cannot create a generic array of E
    }

    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;
        return result;
    }


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

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

如黑体所示的方法中报错

如第25条所描述的那样,咱们不能直接创建一个不可具体化类型的数组,如E.当咱们编写支持泛型的数组的时候,都会有这个问题,解决这个问题的方式有两种:
1. 直接绕开创建泛型数组的禁令,不创建泛型数组,直接创建一个Object的数组,并将它转换为泛型数组类型,然后我们看到错误消失了,但是编译器仍然出现了一条警告。这种用法是合法的,但(整体上而言)不是类型安全的:

Type safety: Unchecked cast from Object[] to E[] 转换警告

编译器不可能证明当前程序是类型安全的,但是咱们自己可以证明。咱们必须确保未受检的转换不能危及到程序的类型安全性。当前的数组也就是 elements变量是保存在一个私有的域中的,绝不会被返回到客户端,或传给任何其他方法。这个数组中保存的唯一元素,是传给push方法的那些元素,他们的类型为E,因此未受检的转换不会有任何危害。(这个例子中的数组是不会直接返回给其他方法或者客户端的,返回的只是传进来的数组元素)

证明了未受检的转换时安全的,要尽可能的小的范围中禁止警告(24条),在当前情况下,构造器只包含未受检的数组创建,因此可以在整个构造器中禁止这条警告,通过增加一条注解来完成禁止,Stack能够正确无误的进行编译,你就可以使用它了,无需显式的转换,也无需担心出现ClassCastExecption异常:

@SuppressWarnings("unchecked")
    public StaticGerneric() {
//        elements = new E[DEFAULT_INITAIL_CAPACITY];
        elements = (E[])new Object[DEFAULT_INITAIL_CAPACITY];
    }

消除Stack中泛型数组创建错误的第二种方法是:将elements域的类型从E[]改为Object[]。这么做会得到一条不同错误:

public class StaticGerneric1<E> {
    public Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITAIL_CAPACITY = 3;

    public StaticGerneric1() {
        elements = new Object[DEFAULT_INITAIL_CAPACITY];
    }

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

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];//编译报错Type mismatch: cannot convert from Object to E
        elements[size] = null;
        return result;
    }


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

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

词句代码将会编译错误;通过把数组中获取到的元素由Object转换成E,可以将错误变为一条警告:

Type mismatch: cannot convert from Object to E

由于E是一个不可具体化的类型,编译器无法再运行时检验转换,你还是可以自己证实未受检的转换时安全的,因此可以禁止该警告,我们只要在包含未受检转换的任务上禁止警告,而不是在整个pop方法上就可以了,如下:

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        @SuppressWarnings("unchecked")
        E result = (E)elements[--size];
        elements[size] = null;
        return result;
    }

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

下面程序示范了泛型Stack类的使用,程序以相反的顺序打印出他的命令行参数,并转换成大写字母。如果要在从堆栈中弹出的元素上调用String的toUpperCase方法,并不需要显式的转换,并且会确保自动生成的转换成功:

绝大多数泛型就像我们Stack一样,因为他们的类型参数没有限定,你可以创建Stack<Object>、Stack<int[]>、Stack<List>,或者任何其他对象引用类型的Stack,注意不能创建基本类型的Satck:企图创建Stack<int>或者Satck<double>会产生一个编译错误。这是JAVA泛型系统根本的局限性,你可以通过使用基本类型的包装类来避开这个局限性。

有一些泛型限制了可允许的类型参数值,例如:考虑java.util.concurrent.DelayQueue,其声明如下:

class DelayQueue<E extends Delayed> implements BlockingQueue<E>;  

类型参数列表要求实际的类型参数E必须是java.util.concurrent.Delayed的一个子类型
它允许DelayedQueue实现及其客户端在DelayedQueue的元素上利用Delayed方法,无需显式转换,也没有出现ClassCastExecption异常的风险。类型参数E被称作有限制的类型参数,注意:子类型关系确定了,每个类型都是他的子类型,因此创建DelayQueue是合法的。

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

总结

1使用泛型比使用需要在客户端代码中进行类型转换的类型来的更加安全,也更加容易,所以咱们涉及类的时候,尽量做成有泛型的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,581评论 18 399
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,254评论 0 16
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 本文大量参考Thinking in java(解析,填充)。 定义:多态算是一种泛化机制,解决了一部分可以应用于多...
    谷歌清洁工阅读 456评论 0 2
  • 前言:我想每一个父母都希望自己的孩子是优秀的,尽管孩子本身已经很优秀,然而我相信天下没有百分百乖既乖巧听话又聪明健...
    伊人yoyo阅读 588评论 1 2