fxleyu / west-world

This is a repository for the records of books, films, teleplay and so on.
https://fxleyu.github.io/
0 stars 0 forks source link

[阅读笔记][EJ] 第 5 章 泛型 #63

Open fxleyu opened 6 years ago

fxleyu commented 6 years ago

来自《Effective Java》 #43 的阅读笔记。

Java 1.5 发行版本中增加了 泛型(Generic)。在没有泛型之前,从集合中读取到的每一个对象都必须进行转换。如果有人不小心插入了类型错误的对象,在运行时的转换处理就会出错。有了泛型之后,可以告诉编译器每个集合中接受哪些对象类型。编译器自动地为你的插入进行转化,并在编译期告知是否插入了类型错误的对象。这样可以使程序既更加安全,也更加清楚,但是要享有这些优势有一定的难度。

本章就是教你如何最大限度地享有这些优势,又能使整个过程尽可能地简单化。

本章目录

fxleyu commented 6 years ago

第 23 条:请不要在新代码中使用原生态类型

术语 示例 所在条目
参数化的类型 List<String> 第 23 条
实际类型参数 String 第 23 条
泛型 List<E> 第 23、26 条
形式类型参数 E 第 23 条
无限制通配符类型 List<?> 第 23 条
原生态类型 List 第 23 条
有限制类型参数 <E extends Number> 第 26 条
递归类型参数 <T extends Comparable<T>> 第 27 条
有限制通配符类型参数 List<? extends Number> 第 28 条
泛型方法 static <E> List<E> asList(E[] a) 第 27 条
类型令牌 String.class 第 29 条

如上所述,如果不提供类型参数,使用集合类型和其他泛型也仍然是合法的,但是不应该这么做。如果使用原生类型,就失掉了泛型在安全性和表述性方面的所有优势。 移植兼容性(Migration Compatibility) 促成了支持原生态类型的决定。

原生态类型 List 和参数化的类型 List<Object> 之间到底有什么区别呢?不严格地说,前者逃避了泛型检查,后者则明确声明告知编译期,它能够持有任意类型的对象。

虽然你可以将 List<String> 传递给类型 List 的参数,但是不能将它传给类型 List<Object> 的参数。泛型有子类型(subtyping)的规则, List<String> 是原生态类型 List 的一个子类型,而不是参数化类型 List<Object> 的子类型。

如果使用像 List 这样的原生态类型,就会失掉类型安全性,但是如果使用像 List<Object> 这样的参数化类型,则不会。

        List rawTypeList = Lists.newArrayList();
        List<Object> genericList = Lists.newArrayList();

        List<String> stringList = Lists.newArrayList();
        rawTypeList = stringList;
        genericList = stringList; // ERROR

ERROR 信息如下:

Incompatible types.
Required: List <java.lang.Object>
Found: List <java.lang.String>

通配符类型是安全的,原生态类型则不安全。由于可以将任何元素放进使用原生态类型的集合中,因此很容易破坏该集合的类型约束条件;但 不能将任何元素(除了 null 之外)放到 Collection<?> 中。

你不仅无法将任何元素(除 null 之外)放进 Collection<?> 中,而且根本无法猜测你会得到哪种类型的对象。要是无法接受这些限制,就可以使用 泛型方法(generic method) 或者 有限制的通配符类型(bounded wildcard type)

不要再新代码中使用原生态类型,这条规则有两个小小的例外,两者都源于“ 泛型信息可以在运行时被擦除 ”这一事实。

  1. 在类文字(class literal)中必须使用原生态类型。 规范不允许使用参数化类型(虽然允许数组合基本类型)。换句话说,List.class, String.classint.class 都合法,但是 List<String>.classList<?>.class 则不合法。
  2. 由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限通配符类型上使用 instanceof 操作符是非法的。
        if (o instanceof List) {
            List<?> list = (List<?>) o;
        }

总结

使用原生态类型会在运行时导致异常,因此不要在新代码中使用。原生态类型只是为了引入泛型之前的遗留代码进行兼容和互用而提供的。

让我们做个快速的回顾:Set<Object> 是个参数化类型,表示可以包含任何对象类型的一个集合;Set<?> 则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set 则是个原生态类型,它脱离了泛型系统。前两种是安全的,最后一种不安全。

fxleyu commented 6 years ago

第 24 条:消除非受检警告

用泛型编程时,会遇到很多编译器警告:非受检强制转化警告(unchecked cast warnings)、非受检方法调用警告、非受检普通数据创建警告、以及非受检转换警告(unchecked conversion warnings)。

要尽可能地消除每一个非受检警告。 如果消除了所有警告,就可以确保代码是类型安全的,这是一件很好的事情。这意味着不会在运行时出现 ClassCastException 异常,你会更加自信自己的代码可以实现预期的功能。

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下才)可以使用一个 @SuppressWarnings("unchecked") 注解来禁止这条警告。 如果忽略(而不是禁止)明知道是安全的非受检警告,那么当新出现一条真正有问题的警告时,你也不会注意到。新出现的警告就会淹没在所有的错误警告当中。

SuppressWarnings 注解可以用在任何粒度的级别中,从单独的局部变量声明到整个类都可以。应该始终在尽可能小的范围中使用 SuppressWarnings 注解。 永远不要在整个类上使用 SuppressWarnings,这样做可能会掩盖了重要的警告。

如果你发现自己在长度不知一行的方法或者构造器中使用 SuppressWarnings 注解,可以将它移到一个局部变量的声明中。虽然你必须声明一个新的局部变量,不过这么做还是值得的。

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

总结

非受检警告很重要,不要忽略它们。每一条警告都表示可能在运行时抛出 ClassCastException 异常。要尽最大努力消除这些警告。如果无法消除非受检异常,同时可以证明引起警告的代码是类型安全的,就可以在尽可能小的范围中,用 @SuppressWarnings("unchecked") 注解禁止该警告。要用注解把禁止该警告的原因记录下来。

fxleyu commented 6 years ago

第 25 条:列表优先于数组

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

由于上述这些根本的区别,因此数组和泛型不能很好地混合使用。

总结

总而言之,数组和泛型有着非常不同的类型规则。数组是协变且可以具体化的;泛型是不可变的且可以被擦除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全。反之,对于泛型也是一样。一般来说,数组和泛型不能很好地混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应就应该是用列表代替