编辑按:《Java in a Nutshell, 5th Edition》覆盖了jdk5.0中很多变化和新特征,其中最重要的就是泛型。在本文的第一部分,作者David Flanagan介绍了如何使用泛型;而在第二部分,作者描述了如何写你自己的泛型和泛型方法。
Java5.0的新特性之一是引入了泛型类型和泛型方法。一个泛型类型通过使用一个或多个类型变量来定义,并拥有一个或多个使用一个类型变量作为一个参数或者返回值的占位符。例如,类型java.util.List<E是一个泛型类型:一个list,其元素的类型被占位符E描述。这个类型有一个名为add()的方法,被声明为有一个类型为E的参数,同时,有一个get()方法,返回值被声明为E类型。
为了使用泛型类型,你应该为类型变量具体指明实际的类型,形成一个就像List<String类似的参数化类型。[1]指明这些额外的类型信息的原因是编译器据此能够在编译期为您提供很强的类型检查,增强您的程序的类型安全性。举个例子来说,您有一个只能保持String对象的List,那么这种类型检查就能够阻止您往里面加入String[]对象。同样的,增加的类型信息使编译器能够为您做一些类型转换的事情。比如,编译器知道了一个List<String有个get()方法,其返回值是一个String对象,因此您不再需要去将返回值由一个Object强制转换为String。
Java.util包中的集合类在java5.0中已经被做成了泛型,也许您将会在您的程序中频繁的使用到他们。类型安全的集合类就是一个泛型类型的典型案例。即便您从没有定义过您自己的泛型类型甚至从未用过除了java.util中的集合类以外的泛型类型,类型安全的集合类的好处也是极有意义的一个标志——他们证实了这个主要的新语言特性的复杂性。
我们从探索类型安全的集合类中的基本的泛型用法开始,进而研究更多使用泛型类型的复杂细节。然后我们讨论类型参数通配符和有界通配符。描绘了如何使用泛型以后,我们阐明如何编写自己的泛型类型和泛型方法。我们对于泛型的讨论将结束于一趟对于JavaAPI的核心中重要的泛型类型的旅行。这趟旅程将探索这些类型以及他们的用法,旅程的目的是为了让您对泛型如何工作这个问题有个深入的理解。
类型安全集合类
Java.util类包包含了Java集合框架(Java Collections Framework),这是一批包含对象的set、对象的list以及基于key-value的map。第五章将谈到集合类。这里,我们讨论的是在java5.0中集合类使用类型参数来界定集合中的对象的类型。这个讨论并不适合java1.4或更早期版本。假如没有泛型,对于集合类的使用需要程序员记住每个集合中元素的类型。当您在java1.4种创建了一个集合,您知道您放入到集合中的对象的类型,但是编译器不知道。您必须小心地往其中加入一个合适类型的元素,当需要从集合中获取元素时,您必须显式的写强制类型转换以将他们从Object转换为他们真是的类型。考察下边的java1.4的代码。
public static void main(String[] args) {// This list is intended to hold only strings.// The compiler doesn't know that so we have to remember ourselves.List Wordlist = new ArrayList();// Oops! We added a String[] instead of a String.// The compiler doesn't know that this is an error.wordlist.add(args);// Since List can hold arbitrary objects, the get() method returns// Object.Since the list is intended to hold strings, we cast the// return value to String but get a ClassCastException because of// the error above.String word = (String)wordlist.get(0);}
泛型类型解决了这段代码中的显示的类型安全问题。Java.util中的List或是其他集合类已经使用泛型重写过了。就像前面提到的, List被重新定义为一个list,它中间的元素类型被一个类型可变的名称为E的占位符描述。Add()方法被重新定义为期望一个类型为E的参数,用于替换以前的Object,get()方法被重新定义为返回一个E,替换了以前的Object。
在java5.0中,当我们申明一个List或者创建一个ArrayList的实例的时候,我们需要在泛型类型的名字后面紧跟一对“<”,尖括号中写入我们需要的实际的类型。比如,一个保持String的List应该写成“List<String”。需要注重的是,这非常象给一个方法传一个参数,区别是我们使用类型而不是值,同时使用尖括号而不是圆括号
Java.util的集合类中的元素必须是对象化的,他们不能是基本类型。泛型的引入并没有改变这点。泛型不能使用基本类型:我们不能这样来申明——Set<char或者List<int。记住,无论如何,java5.0中的自动打包和自动解包特性使得使用Set<Character或者List<Integer和直接使用char和int值一样方便。(查看第二章以了解更多关于自动打包和自动解包的细节)。
在Java5.0中,上面的例子将被重写为如下方式:
public static void main(String[] args) {// This list can only hold String objectsList<String wordlist = new ArrayList<String();// args is a String[], not String, so the compiler won't let us do thiswordlist.add(args);// Compilation error!// We can do this, though.// Notice the use of the new for/in looping statementfor(String arg : args) wordlist.add(arg);// No cast is required.List<String.get() returns a String.String word = wordlist.get(0);}
值得注重的是代码量其实并没有比原来那个没有泛型的例子少多少。