java 泛型之不要使用原生态类型

来源:互联网 发布:java array list 编辑:程序博客网 时间:2024/05/18 00:33
从java1.5发行版本中增加了泛型(Generic)。在没有泛型之前,从集合中读取到的每一个对象都是必须进行转换的。如果不小心插入了类型错误的对象,在运行时的转换处理就会出错。有了泛型以后,可以告诉编译器每个集合中接收哪些对象类型。编译器就会自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。这样可以是程序既更加安全,又更加清楚,但是要享用这些优势有一定的难度。特别是灵活的运用泛型,根据这几天的学习和实际运用,做一个总结吧。

什么是泛型?

先来介绍一些术语。声明中具有一个或多个类型参数(type parameter)的类或者接口,就是泛型类或者接口。例如,List接口就只有单个类型参数E,表示列表的元素类型。从技术的角度来看,这个接口的名称应该是指现在的List<E>,但是咱们经常把它简称为List。泛型类和接口统称为泛型(generic type)。

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

原生态类型

每个泛型都会定义一个原生态类型(Raw Type),即不带任何实际类型参数的泛型名称。例如,与List<String>对应的原生态类型时List。原生态类型就像从类型声明中删除了所有泛型信息一样。实际上,原生态类型List与java平台没有泛型之前的接口类型List完全一样。

在没有泛型之前,定义一个保存int类型的对象如下:

List  nameList = new ArrayList();nameList.add(1);

但如果添加一行nameList.add("java");也是能编译成功的。

切记:错误要尽早发现,最好是编译时返现。

但是在本例中,直到运行起来,你从nameList中获取int item0 = Integer.parsetInt(nameList.get(i));当你运行到“java”时就会出现ClassCastException错误。
有了泛型之后,就可以利用改进后的类型声明来替代集合中的这种情况,告诉编译器集合里只能存放什么类型参数。

List<Integer> nameList = new ArrayList<Integer>();

再添加nameList.add("java"); 编译器就会给出错误警告。而且,获取集合中的元素时不需要再手动转换了(Integer.parsetInt()),直接可以int item0 = nameList.get(i);

List<Object> 引发的子类型化

如上所述,虽然不应该在新代码中使用想List这样原生态类型,使用参数化的类型以允许插入任意对象,如List<Object> ,这还是可以的。原生态类型List和参数化类型List<Object>之间到底有什么区别呢?

不严格的说,前者逃避了泛型的检查,后者明确告诉编译器,它能够持有任意类型的对象。虽然你可以将List<String>传递给类型List的参数,但是不能将它传给类型List<Object>的参数。

子类型化(subtyping)

泛型有子类型化(subtyping)的规则,List<String> 是原生态类型List的一个子类型,而不是参数化类型List<Object> 的子类型。因此,如果使用像List这样的原生态类型,就会丢失类型安全性,但是如果使用像List<Object>这样的参数化类型,则不会丢失类型安全性。如下图原生态类型实例:

这里写图片描述

代码如下:

public static void main(String[] args) {        List<String> strList = new ArrayList<String>();        unsafeAdd(strList, new Integer(20));        String str = strList.get(0);}private static void unsafeAdd(List list, Object o){    list.add(o);}

这个程序是可以编译的,但是因为它使用了原生态类型List,就会收到一个警告如下图:
这里写图片描述

实际上,如果运行这段程序,在程序试图将strList.get(0)的返回结果转换为一个String类型时,就会收到一个ClassCastException异常。这是一个编译器生成的转换,因此一般保证会成功,但是我们这个例子忽略了一个编译器警告,就会为此付出代价(错误)。

如果在unsafeAdd声明中用参数化类型List<Object> 代替生态类型List,编译时就会发现出现了错误,如下图:
这里写图片描述

也许,在不确定或者不在乎集合中的元素类型的情况下,你也许会使用原生态类型。例如,假设想编写一个方法,它会有两个集合(set),并从中返回他们共有的元素数量。如果你对泛型不太熟悉的话,可以考虑一下的方式编写这种方法,如下图:
这里写图片描述
哈哈:出现了警告了,不管它,这个例子探讨的不是这个问题哦
这个方法可以实现这个功能,如果方法的参数类型都是一致的话。但是很危险(原生态类型)。

初见 无限制的通配符类型

有了泛型之后呢会有一种比较安全的替代方法,称作无限制的通配符类型(Unbounded wildcard type)。

如果要使用泛型,但不确定或者不关心实际的类型参数,就可以使用一个问号替代。例如,泛型Set<E> 的无限制通配符类型为 Set<?>(读作“某个类型的集合”)。这是最普通的参数化Set集合,可以持有任何集合。如下图使用了无限制通配符类型时的情形:
这里写图片描述
哈哈,警告没有了吧。

无限制通配符类型Set<?> 和原生态类型Set的区别

这个问号真正起到作用了吗?这个是当然起到作用的,因为通配符类型时安全的,原生态类型不安全。由于可以将任何元素放进原生态类型的集合中,因此很容易破坏改集合的类型约束条件;

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

第一个列外:在类文字(class literal)中必须使用原生态类型。规范不允许使用参数化类型(虽然允许数组类型和基本类型)。换句话说,List.class,String[].classint.class都合法,但是List<Sring.class 和 List<?>.class>则不合法。
第二个例外:与instanceof操作符有关。由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配符类型上使用instanceof操作符是非法的。用无限制通配符类型代替原生态类型,对于instanceof操作符的行为不会产生任何影响。在这种情况下,尖括号和问号就显得多余了。

总之,使用原生态类型会在运行时导致异常,因此不要再新代码中使用。但就有人问既然这么不推荐使用,为什么还要存在原生态类型呢?原生态类型只是为了与引入泛型之前的遗留代码进行兼容和互换而提供的。让我们做个快速的回顾:Set<Object> 是个参数化类型,所以可以包含任何对象类型的一个集合;Set<?> 则是一个通配符类型,表示只能包含某种位置对象类型的一个集合;Set则是原生态类型,它脱离了泛型系统。前两个是安全的,最后一种不安全。

1 0
原创粉丝点击