《Effective Java》第5章 泛型

Posted ITRoad

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Effective Java》第5章 泛型相关的知识,希望对你有一定的参考价值。

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

声明中具有一个或者多个类型参数( type parameter)的类或者接口,就是泛型(generic)类或者接口。

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

最后点,每个泛型都定义一个原生态类型[raw type],即不带任何实际类型参数的泛型名称。例如,List相对应的原生态类型是List。原生态类型就像从类型声明中删除了所有泛型信息一样。

如果使用原生态类型,就失掉了泛型在安全性和表述性方面的所有优势。

从Java 1.5发行版本开始,Java就提供了一种安全的替代方法,称作无限制的通配符类型(unbounded wildcard type). 如果要使用泛型,但不确定或者不关心实际的类型参数,就可以使用一个问号代替。例如,泛型Set的无限制通配符类型为Set<>(读作“某个类型的集合”)。

通配符类型是安全的,原生态类型则不安全。

不能将任何元素(除了null之外)放到Colllection<>中。

不要在新代码中使用原生态类型,这条规则有两个小小的例外,两者都源于“泛型信息可以在运行时被擦除”(见第25条)这一事实。在类文字(class literal)中必须使用原生态类型。规范不允许使用参数化类型(虽然允许数组类型和基本类型)。换句话说,List.class, String[].class和int.class都合法,但是List.class和List<>.class则不合法。

这条规则的第二个例外与instanceof操作符有关。由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceo躁作符是非法的。用无限制通配符类型代替原生态类型,对instanceof操作符的行为不会产生任何影响。在这种情况下,尖括号(<>)和问号(?)就显得多余了。下面是利用泛型来使用instanceof操作符的首选方法:

第24条: 消除非受检警告

SuppressWarnings注解可以用在任何粒度的级别中,从单独的局部变量声明到整个类都可以。应该始终在尽可能小的范围中使用SuppressWarnings注解。它通常是个变量声明、或是作常简短的方法或者构造器。永远不要在整个类上使用SuppressWarnings,这么做可能会掩盖了重要的警告。

如果你发现自己在长度不止行的方法或者构造器中使用了SuppressWarnings注解,可以将它移到一个局部变最的声明中。

将SuppressWarnings注解放在return语句中是非法的,因为它不是一个声明。

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

第25条:列表优先于数组

数组协变性的缺陷
数组与泛型相比。有两个重要的不同点。首先,数组是协变的(covariant)。即表示如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型。相反,泛型则是不可变的(invariant): 对于任意两个不同的类型Type1和Type2 , List既不是List的子类型,也不是List的超类型。你可能认为,这意味着泛型是有缺陷的,但实际上可以说数组才是有缺陷的。

数组是具体的,泛型是擦除的
数组与泛型之间的第二大区别在于,数组是具体化的(reified )。因此数组会在运行时才知道并检查它们的元素类型约束。相比之下,泛型则是通过擦除(erasure)来实现的。因此泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型信息。

由于上述这些根本的区别,因此数组和泛型不能很好地混合使用。例如,创建泛型、参数化类型或者类型参数的数组是非法的。
非法定义的示例:
new List[]、new List[]和new E[]。这些在编译时都会导致一个generic array creation。

Note:
为什么创建泛型数组是非法的?因为它不是类型安全的。要是它合法,编译器在其他正确的程序中发生的转换就会在运行时失败,并出现一个CIassCastException异常。这就违背了泛型系统提供的基本保证。
一个反例:
假设以下第一行是可以编译通过的,那么会在第五行得到CIassCastException。

创建无限制通配类型的数组是合法
从技术的角度来说,像E, List和List这样的类型应称作不可具体化的(non-reifiaBle)类型。直观地说,不可具体化的(non-reifiable)类型是指其运行时表示法包含的信息比它的编译时表示法包含的信息更少的类型。唯一可具体化的(reifiable )参数化类型是无限制的通配符类型,如List<>和Map<>。虽然不常用,但是创建无限制通配类型的数组是合法的。

列表:用性能换取类型安全性
当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List,而不是数组类型E[]。这样可能会损失一些性能或者简洁性。但是换回的却是更高的类型安全性和互用性。

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

第27条:优先考虑泛型方法

类型导出
泛型方法的一个显著特性是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必须指定的。编译器通过检查方法参数的类型来计算类型参数的值。这个过程称为类型导出(type inference)。

相关的模式是泛型单例工厂(generic singleton factory )。有时,会需要创建不可变但又适合于许多不同类型的对象。

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

例子:
假设我们想要在Stack中增加一个方法,让它按顺序将一系列的元素全部放到堆栈中。

考虑以下反例:

但是,如果尝试这么做,就会在以下代码得到错误消息。因为如前所述,参数化类型是不可变的。

幸运的是,有一种解决办法。Java提供了一种特殊的参数化类型,称作有限制的通配符类型(bounded wildcard type),来处理类似的情况。pushAll的输入参数类型不应该为“E的Iterable接口”,而应该为“E的某个子类型的Iterable接口”,有一个通配符类型正符合此意:
Iterable.

修改后的代码如下:

"E的某种超类的集合”(这里的超类是确定的,因此E是它自身的一个超类), 仍然有一个通配符类型正是符合此意:Collection<? super E)。

PECS
结论很明显口为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。如果某个输入参数既是生产者。又是消费者,那么通配符类型对你就没有什么好处了:因为你需要的是严格的类型匹配,这是不用任何通配符而得到的。

下面的助记符便于让你记住要使用哪种通配符类型:
PEGS表示producer-extends, consumer-super.

换句话说,如果参数化类型表示一个T生产者,就使用,如果它表示一个T消费者,就使用

Note:
不要用通配符类型作为返回类型。

总而言之,在API中使用通配符类型虽然比较需要技巧,但是使API变得灵活得多。如果编写的是将被广泛使用的类库,则一定要适当地利用通配符类型。记住基本的原则:producer-extends, consumer-super (PECS)。还要记住所有的comparable和comparator都是消费者。

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

异构容器是指能够容纳不同类型对象的容器。像我们通常用的List、Map等容器,它们的原生态类型本身就是异构容器,一旦给它们设置了泛型参数,例如List、Map,它们就不再是异构容器。但是,原生态类型是不安全的,你无法知道从容器取出的类型到底是什么,很容易导致错误。因此,如何构建类型安全的异构容器就成了一个重要的话题。

使用Map实现类型安全的异构容器
我们将要实现一个Favorites类,用来对每个类型保存一个最喜欢的实例。它的API如下:

  1. public class Favorites {
  2. public <T> void putFavorite(Class<T> type, T instance);
  3. public <T> T getFavorite(Class<T> type);
  4. }

Favorite,的实现小得出奇。它的完整实现如下

但是,恶意的客户端可以破坏Favorites实例的类型安全。如果客户端传入原生态的Class对象和不一致的值对象,则会在getFavorite的cast时抛出ClassCastException异常。不过好在我们可以对这一情况加以约束。只需要在put时使用一个动态的转换就可以了:
将put方法更新为以下方法:

参考资料

【1】类型安全的异构容器 作者:金戈大王

以上是关于《Effective Java》第5章 泛型的主要内容,如果未能解决你的问题,请参考以下文章

[读书笔记]《Effective Java》第9章异常

Effective Java 第三版——29. 优先考虑泛型

Effective Java 第三版——32.合理地结合泛型和可变参数

《Effective Java》第7章 方法

《Effective Java》第10章 发并

《Effective Java》第9章 异常