首页 > 学院 > 开发设计 > 正文

[Effective Java]第五章 泛型

2019-11-14 22:44:29
字体:
来源:转载
供稿:网友
[Effective java]第五章 泛型声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.VEVb.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.VEVb.com/jiangzhengjun/p/4255654.html 第五章 泛型23、 请不要在新代码中使用原生态类型

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

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

每个泛型都定义一个原生态类型,即不带任何实际类型参数的泛型名称,也是没有泛型之前的类型。

泛型能将运行时期的错误提前到编译时期检测。

如果使用原生态类型,就失掉了泛型在安全性和表述性方面的所有优势。既然不应该使用原生态类型,为什么Java设计还要允许使用它们呢?这是为了提供兼容性,要兼容以前没有使用泛型的Java代码。

原生态类型List和参数化的类型List<Object>之间到底有什么区别呢?不严格地说,前者逃避了泛型检查,后者则明确告知编译器,它能够持有任意类型的对象。泛型有子类型化的规则:List<String>是原生态类型List的一个子类型,而不是参数化类型List<Object>的子类型(见25条)。因此,如果用不用像List这样的原生态类型,就会失掉类型安全性,但是如果使用像List<Object>这样的参数化类型,则不会。

在无限制通配类型Set<?>和原生态类型Set之间有什么区别呢?由于可以将任何元素放进使用原生态类型的集合中,因此很容易破坏该集合的类型约束条件;但不能将任何元素(除了null之外)放到Collection<?>中。

“不要在新代码中使用原生态类型”,这条规则有两个例外,这是因为“泛型信息在运行时就会被擦除”。在获取类信息中必须使用原生态类型(数组类型和基本类型也算原生态类型),规范不允许使用参数化类型。换句话说:List.class,String[].class和int.class都是合法,但是List<String>.class和List<?>.class都是不合法的。这条规则的第二个例外与instanceof操作符有关,由于泛型信息在运行时已被擦除,因此在参数化类型而不是无限制通配符类型(如List<?>)上使用instanceof操作符是非法的,用无限制通配符类型代替原生态类型,对instanceof操作的行为不产生任何影响。在这种情况下,尖括号<>和问号?就显得多余了。下面是利用泛型来使用instanceof操作符的首先方法:

if(o instanceof set){

Set<?> m = (Set<?>)o;

// ...

}

注意,一旦确定这个o是个Set,就必须将它转换成通配类型Set<?>,则不是转换成原生态类型Set,否则Set会引起编译时警告。

总之,使用原生态类型会在运行时导致异常,因此不要在新代码中使用。原生态类型只为了与引入泛型之前的遗留代码进行兼容和互用而提供的。另外Set<Object>是个参数化类型,表示可以包括任何对象类型的一个集合;Set<?>则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set则是个原生态类型,它脱离了泛型系统。前两者是安全的,最后一种不安全。

术语介绍:

原生态类型:List

参数化的类型:List<String>

泛型:List<E>

有限制类型参数:List<E extends Number>

形式类型参数:E

无限制通配符类型:List<?>

有限制通配符类型:List<? extends Number>

递归类型限制:List <T extends Comparable<T>>

泛型方法:static<E> List<E> asList(E[] a)

24、 消除非受检警告

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

要尽可能地消除每一个非受检警告。如果消除了所有警告,就可以确保代码是类型安全的。

如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有在这种情况下才可以用一个@SupPRessWarnings("unchecked")注解来禁止这条警告。

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

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

25、 列表优先于数组

数组与泛型相比,有两个重要的不同点:首先,数组是协变的,如Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型。但泛型则是不可变的,对于任意两个不同的类型Type1和Type2,List<Type1>与List<Type2>没有任何父子关系。

下面的代码片段是合法的:

Object[] objectArray = new Long[1];

objectArray[0]= "";//运行时抛异常

但下面这段代码则在编译时就不合法:

List<Object> ol = new ArrayList<Long>();//编译时就不能通过

ol.add("");

利用数组,你会在运行时才可以发现错误,而利用列表,则可以在编译时发现错误。而我们最好是编译时发现错误,及早的处理它。

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

由于上述这些根本的区另,因此数组和泛型不能很好混合使用。例如,创建泛型、参数化类型或者类型参数的数组是非法的,如:new List<E>[]、new List<String>[]、new E[]都是非法的。

为什么不允许创建泛型数组呢?看具体例子:

List<String>[] stringLists= new List<String>[1];//1

List<Integer> intList = Arrays.asList(42); //2

Object[] objects = stringLists; //3

objects[0] = intList; //4

String s = stringLists[0].get(0); //5

这里首先假设第一行可以,其他行本身编译是没有问题的,但运行到5行时肯定会抛出ClassCastException异常。为了防止出现这种情况,创建泛型数组第1行就不允许了。

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

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

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

26、 优先考虑泛型

考虑第6条中的堆栈实现,将它定义成泛型类。第一种是将elements定义成类型参数数组:

publicclassStack<E> {

privateE[] elements;//定义成类型参数数组

privateintsize = 0;

privatestaticfinalintDEFAULT_INITIAL_CAPACITY= 16;

//通过pust(E)我们只能将E类型的实例放入到elements中,这已充分确保类型

//安全,所以这里可以强转。但是运行时数组的类型不是E[],它仍然是Objct[]类型的!

@SuppressWarnings("unchecked")

publicStack() {

//elements =new E[DEFAULT_INITIAL_CAPACITY];//不能创建类型参数数组

/*

*编译器不可能证明你的程序是类型安全的,但是你可以证明。你自己必须确保未受

*检的转换不会危及到程序的类型安全。因为elements保存在一个私有的域中,永远

*不会返回到客户端。或者传给任何其他方法。这个数组中保存的唯一元素,是传给

* push方法的那些元素,它们的类型为E,因此未受检的转换不会有任何危害。一旦

*你证明了未受检的转换是安全的,就要在尽可能小的范围中禁警告。然后你就可以

*使用的它了,无需显示转换,也不需担心会出ClassCastException异常。

*/

elements = (E[])newObject[DEFAULT_INITIAL_CAPACITY];//这里会有

}

publicvoidpush(E e) {

ensureCapacity();

elements[size++] = e;

}

publicE pop() {

if(size == 0)

thrownewEmptyStackException();

E result = elements[--size];

Word-spacing: 0px; text-transfo

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表