准备好迎接泛型
泛型介绍
作者:Paul Mingardi
November 2003
泛型已经在其他的语言中使用了很多年了,通常也被称为参数化类型。现在在广大Java开发人员的要求下,泛型终于被添加进了Java语言中,并且将伴随J2SE1.5一起发布。
Lists的问题
有了泛型,容器类型(例如Lists)将不是用来操作指定类型的数据,而是根据容器定义的时候指定的数据类型来操作一组同质(homogeneous)的数据。这样对Java开发人员有什么帮助呢?
理解泛型的最好的办法是看看一些将从泛型中得到好处的代码。下面的的例子是用当前的Java语言规范写的(不带泛型),包含了一个String类型元素的List和Integer类型(不是基本类型int)元素的List。因为两个元素都是Object类的子类,并且Java有多态引用,所以你可以往List里面塞任何的类型。不幸的事情即将发生,从编译器的角度来看,两个List中的元素都是Object类型的,所以唯一能区别两个List中的类型的方法就是使用运行时类型识别(即使用转型cast)。更坏的事情是,如果一个开发人员混淆了两个List中元素的类型并且执行了一个非法的转型,这样的错误只有在运行时才能被发现。
下面是这样的一段代码。在第一个例子中(第13行到20行),你可能会确信你是在操作一个Integer类型的List,虽然事实上是一个String类型的List。在第二部分(第11行和22到27行),你可能以为你在对同质的String类型进行操作,但事实上这是一个混合了String和Integer类型的异质List。除非你为每个元素类型建立一个List(这样其实是破坏了面向对象的特性),你将无法静态的限制List只能容纳同质的元素。在这个例子中,这样的错误还很容易就被发现,但是在一个大型的系统中,这将是你的一个大问题。
1..
2. List stringList = new LinkedList();
3. List integerList = new LinkedList();
4.
5. integerList.add(new Integer(1));
6. integerList.add(new Integer(2));
7.
8. stringList.add(new String("I am a String"));
9.
10. // 没什么能限制元素是同质的
11. stringList.add(new Integer(1));
12.
13. Iterator listIterator = integerList.iterator();
14.
15. // 编译器不关心List的返回类型和不合法的转型
16. // 开发人员本意是想遍历String的这个List
17. while(listIterator.hasNext()) {
18.
19. // 只有在运行时才能捕捉到这个不合法的转型错误。
20. String item = (String)listIterator.next();
21. }
22.
23. listIterator = stringList.iterator();
24. // 没有同质元素的保证
25. while (listIterator.hasNext()) {
26. // 因为异质元素,将导致运行时错误
27. String item = (String)listIterator.next();
28. }
29..
泛型的优点
有了泛型,你就能得到和上面的例子所要的动态特性,有了一个很强的静态类型检查,编译器知道两个List的不同,因为他们容纳不同类型的元素,并且每个List只能容纳同质的一组元素。下面的例子对上面的代码有了一些改进,使用了泛型。你可以在注释中看到,所有的错误都在编译时被捕捉了。先不要去关心语法,在下面我们将讨论到语法。
对比两个例子,你就会注意到,在泛型中添加了额外的类型信息,这些信息将指引编译器去检查容器中应该包含什么类型的数据。
1..
2. import java.util.LinkedList;
3. import java.util.Collections;
4. import java.util.Iterator;
5.
6. public class genericsExample2{
7.
8. static public void main(String[] args) {
9. LinkedList<String> stringList = new LinkedList<String>();
10. LinkedList<Integer> integerList = new LinkedList<Integer>();
11.
12. integerList.add(new Integer(1));
13. integerList.add(new Integer(2));
14.
15. stringList.add(new String("I am a String"));
16. stringList.add(new Integer(1)); // 这将导致一个编译错误
17.
18. /* genericsExample2.java:16: cannot resolve symbol
19. ** symbol : method add (java.lang.Integer)
20. */
21.
22. Iterator<Integer> listIterator = integerList.iterator();
23. String item;
24. while(listIterator.hasNext()) {
25. item = listIterator.next(); // 这将导致一个编译错误
26.
27. /* genericsExample2.java:25: incompatible types
28. ** found : java.lang.Integer
29. ** required: java.lang.String
30. */
31. }
32.
33. listIterator = stringList.iterator(); // 这将导致一个编译错误
34.
35. /* genericsExample2.java:33: incompatible types
36. ** found : java.util.Iterator<java.lang.String>
37. ** required: java.util.Iterator<java.lang.Integer>
38. */
39. // the iterator is guaranteed to be homogeneous
40. while (listIterator.hasNext()) {
41. item = listIterator.next();
42.
43. /* genericsEx2.java:41: incompatible types
44. ** found : java.lang.Integer
45. ** required: java.lang.String
46. */
47. }
48. } // main
49. } // class genericsExample2
通过这个例子,你应该能明白为什么泛型是对Java语言的改进中呼声最高的。下面是一些使用泛型后的好处:
通过静态类型检查来获得平滑的动态绑定。编译时发现的错误比运行时发生的错误容易纠正。
容器的模棱两可性减少了,代码的可读性增强了。
使用更少的转型使得代码更加清晰。
基本语法一览
到这,你只看到了泛型化的容器类,其实你可以声明泛型化的接口,类,和方法,下面描述了你可能会用到的语法。
语法
参数化类型(Paramaterized Type)
Vector<String> stringVector = new Vector<String>
List<Integer> integerList = new List<Integer>
接口(Interface)
interface List<Element> implements MyInterface{...}
类(Class)
class MyList<Element> {...}
class MyList<Element> implements List<Element> {...}
方法(Method)
boolean containsBoth(Element a, Element b);
static <Element> boolean swap(List<Element> list, int i, int j);
下面的代码使用类似的语法:MyInterface
开发使用泛型
关于泛型更多的信息,你可以参考JSR14的声明(specification and to the JSR 14)。开发人员今天就可以通过Sun公司提供的实现了泛型的原型编译器来使用泛型代码。这个原型包含了:用扩展后的语言写的范例原代码,一个编译时候需要用到的jar包,一个包含了容器类型的一部分,还包含了一些范例程序和makefile 来帮助编译代码和调用JVM。下面是用C-shell的一些步骤。
如果你还不是一个Java Developer Connection的成员,那么你需要注册一下才能下载。
解压缩包到安装目录/install_dir
- unzip adding_generics-1_3-ea.zip
设置环境变量
setenv JSR14DISTR /install_dir/jsr14_adding_generics-1_3-ea
setenv J2SE14 /jdk_install_dir
运行例子
cd /install_dir/jsr14_adding_generics-1_3-ea/examples
Compile and run the Test.java sample using the makefile.
- make
参考
Early Access Prototype for JSR-14
关于作者
Paul Mingardi 是Sun公司的市场开发工程师,对合作伙伴例如健康关心, 银行和金融方面的技术支持。同时他也和独立软件供应商合作,来帮助他们更加成功的应用Java和其他的Sun的技术。Paul是Sun认证的企业架构师(Sun Certified Enterprise Architect)并且同时拥有7个认证。