《Effective Java》第5章 泛型

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

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

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

最后点,每个泛型都定义一个原生态类型[raw type],即不带任何实际类型参数的泛型名称。例如,List

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

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

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

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

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

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

第24条: 消除非受检警告

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

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

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

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

第25条:列表优先于数组

数组协变性的缺陷

数组与泛型相比。有两个重要的不同点。首先,数组是协变的(covariant)。即表示如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型。相反,泛型则是不可变的(invariant): 对于任意两个不同的类型Type1和Type2 , List

数组是具体的,泛型是擦除的

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

由于上述这些根本的区别,因此数组和泛型不能很好地混合使用。例如,创建泛型、参数化类型或者类型参数的数组是非法的。

非法定义的示例:

new List

Note:

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

一个反例:

假设以下第一行是可以编译通过的,那么会在第五行得到CIassCastException。

创建无限制通配类型的数组是合法

从技术的角度来说,像E, List

列表:用性能换取类型安全性

当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型List

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

第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生产者,就使用

Note:

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

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

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

异构容器是指能够容纳不同类型对象的容器。像我们通常用的List、Map等容器,它们的原生态类型本身就是异构容器,一旦给它们设置了泛型参数,例如List

使用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】类型安全的异构容器 作者:金戈大王

时间: 2024-10-09 22:21:42

《Effective Java》第5章 泛型的相关文章

C++ Primer 读书笔记:第11章 泛型算法

第11章 泛型算法 1.概述 泛型算法依赖于迭代器,而不是依赖容器,需要指定作用的区间,即[开始,结束),表示的区间,如上所示 此外还需要元素是可比的,如果元素本身是不可比的,那么可以自己定义比较函数. 2.常用的泛型算法函数: fill,fill_n, copy, replace, sort, unique, count_if, stable_sort 此外在有一个谓词函数会结合以上的函数使用,像sort, count_if等 3.再谈迭代器 (1)插入迭代器 back_inserter, f

第5章 泛型

5.1 泛型概述 在拆箱时,需要使用类型强制转换运算符. 泛型的名称用字母T作为前缀. 5.2 创建泛型类 public class LinkedList<T> : IEnumerable<T> { public IEnumerator<T> GetEnumerator() { } } 5.3 泛型的功能1  通过default关键字,将null赋予引用类型,将0赋予值类型. T doc = default(T); return doc; 2 约束 public cla

第十一章 泛型算法 C++ PRIMER

vector<int>::const_iterator result = find(vector.begin(). vector.end(),search_value); 如果查找失败,分会end()  如果有两个,会返回哪一个的迭代器? int *reauslt = find(ia,ia+6,search_value); 也可以同样处理字符串 算法要以<algorithm><numeric>,依赖于迭代器和迭代器的算法实现,算法可能改变值,可能移动元素,单从不直接添加

C++ Primer学习总结 第10章 泛型算法

第10章 泛型算法 1.    find()泛型算法使用示例: 2.    只读算法accumulate:对所给范围的元素求和并返回. 注意accumulate的第3个参数决定着它的返回类型. 即如果第3个参数是double,就算迭代器里都是int,最终还是返回double类型的数. 3.    只读算法equal:比较前两个迭代器表示范围的所有元素是否与第3个迭代器表示的对应位置的元素都相同,如果相同返回true.两个容器类型可以不同,保存的元素类型也可以不同,只要元素之间可以比较即可.如st

第08章 泛型

1 /***************** 2 ***第08章 泛型 3 *******知识点: 4 **************1.泛型定义 5 **************2.泛型使用 6 ******************2.1 普通泛型 7 ******************2.2 类型通配符 8 ******************2.3 受限泛型 9 ******************2.4 泛型接口.类.方法 10 ******************2.5 泛型数组 11 *

《JAVA编程思想》学习笔记——第十五章 泛型

在面相对象编程中,多态算是一种泛化机制. 泛型实现了参数化类型的概念. 泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性. 元组 仅一次方法调用就能返回多个对象,你应该经常需要这样的功能吧.可是return语句只允许返回单个对象.因此,解决方法就是创建一个对象,用它来持有想要返回的多个对象.例: public class TwoTuple<A,B> {} public class ThreeTuple<A,B,C> extents TwoTup

第二十一章 泛型(generic)

(1)引言 泛型:是指参数化类型的能力. 例如:有这样一个类:这是一个比较简单并且是很常用的带有泛型的方法. package com.lidd.generic; public class GenericTestSecond { public <T>void one(T t){ } } 下面是几个类,分别是一般类.抽象类.接口(接口也是特殊的类). package com.lidd.generic; public class A { } package com.lidd.generic; pub

C++primer第十一章 泛型算法

标准库容器定义的操作非常少.标准库没有给容器添加大量的功能函数,而是选择提供一组算法,这些算法大都不依赖特定的容器类型,是“泛型”的,可作用在不同类型的容器和不同类型的元素上. 因为它们实现共同的操作,所以称之为“算法”:而“泛型”指的是它们可以操作在多种容器类型上——不但可作用于 vector 或 list 这些标准库类型,还可用在内置数组类型.甚至其他类型的序列上. 11.1. 概述 假设有一个 int 的 vector 对象,名为 vec,我们想知道其中包含某个特定值.解决这个问题最简单的

第12章 泛型

泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即算法重用. 简单的说,开发人员先定义好一个算法,比如排序.搜索.交换.比较或者转换等.但是,定义算法的开发人员并不设定该算法要操作什么数据类型.该算法可以广泛地应用于不同类型的对象.然后,另一个开发人员,只有指定了算法要操作的具体数据类型,就可以开始使用这个现成的算法了.例如,可以用一个排序算法来操作Int32和String等类型对象. 大多数算法都封装在一个类型中,CLR允许创建泛型引用类型和泛型值类型,

编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议98~101)

建议98:建议的采用顺序是List<T>.List<?>.List<Object> List<T>.List<?>.List<Object>这三者都可以容纳所有的对象,但使用的顺序应该是首选List<T>,次之List<?>,最后选择List<Object>,原因如下: (1).List<T>是确定的某一个类型 List<T>表示的是List集合中的元素都为T类型,具体类型在