编辑按:《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中的集合类以外的泛型类型,类型安全的集合类的好处也是极有意义的一个标志?d?d他们证明了这个主要的新语言特性的复杂性。
我们从探索类型安全的集合类中的基本的泛型用法开始,进而研究更多使用泛型类型的复杂细节。然后我们讨论类型参数通配符和有界通配符。描绘了如何使用泛型以后,我们阐明如何编写自己的泛型类型和泛型方法。我们对于泛型的讨论将结束于一趟对于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的集合类中的元素必须是对象化的,他们不能是基本类型。泛型的引入并没有改变这点。泛型不能使用基本类型:我们不能这样来申明?d?dset<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 objects list<string> wordlist = new arraylist<string>(); // args is a string[], not string, so the compiler won't let us do this wordlist.add(args); // compilation error! // we can do this, though. // notice the use of the new for/in looping statement for(string arg : args) wordlist.add(arg); // no cast is required. list<string>.get() returns a string. string word = wordlist.get(0);}
值得注意的是代码量其实并没有比原来那个没有泛型的例子少多少。使用“(string)”这样的类型转换被替换成了类型参数“<string>”。 不同的是类型参数需要且仅需要声明一次,而list能够被使用任何多次,不需要类型转换。在更长点的例子代码中,这一点将更加明显。即使在那些看上去泛型语法比非泛型语法要冗长的例子里,使用泛型依然是非常有价值的?d?d额外的类型信息允许编译器在您的代码里执行更强的错误检查。以前只能在运行起才能发现的错误现在能够在编译时就被发现。此外,以前为了处理类型转换的异常,我们需要添加额外的代码行。如果没有泛型,那么当发生类型转换异常的时候,一个classcastexception异常就会被从实际代码中抛出。
就像一个方法可以使用任意数量的参数一样,类允许使用多个类型变量。接口java.util.map就是一个例子。一个map体现了从一个key的对象到一个value的对象的映射关系。接口map申明了一个类型变量来描述key的类型而另一个类型变量来描述value的类型。举个例子来说,假设您希望做一个string对象到integer对象的映射关系:
public static void main(string[] args) { // a map from strings to their position in the args[] array map<string,integer> map = new hashmap<string,integer>(); // note that we use autoboxing to wrap i in an integer object. for(int i=0; i < args.length; i++) map.put(args[i], i); // find the array index of a word. note no cast is required! integer position = map.get("hello"); // we can also rely on autounboxing to convert directly to an int, // but this throws a nullpointerexception if the key does not exist // in the map int pos = map.get("world");}
象list<string>这个一个参数类型其本身也是也一个类型,也能够被用于当作其他类型的一个类型变量值。您可能会看到这样的代码:
// look at all those nested angle brackets!map<string, list<list<int[]>>> map = getweirdmap();// the compiler knows all the types and we can write expressions// like this without casting. we might still get nullpointerexception// or arrayindexoutofbounds at runtime, of course.int value = map.get(key).get(0).get(0)[0];// here's how we break that expression down step by step.list<list<int[]>> listoflists = map.get(key);list<int[]> listofintarrays = listoflists.get(0);int[] array = listofintarrays.get(0);int element = array[0];
在上面的代码里,java.util.list<e>和java.util.map<k,v>的get()方法返回一个类型为e的list元素或者一个类型为v的map元素。注意,无论如何,泛型类型能够更精密的使用他们的变量。在本书中的参考章节查看list<e>,您将会看到它的iterator( )方法被声明为返回一个iterator<e>。这意味着,这个方法返回一个跟list的实际的参数类型一样的一个参数类型的实例。为了具体的说明这点,下面的例子提供了不使用get(0)方法来获取一个list<string>的第一个元素的方法。
list<string> words = // ...initialized elsewhere...iterator<string> iterator = words.iterator();string firstword = iterator.next();
理解泛型类型
本段将对泛型类型的使用细节做进一步的探讨,以尝试说明下列问题:
不带类型参数的使用泛型的后果
参数化类型的体系
一个关于编译期泛型类型的类型安全的漏洞和一个用于确保运行期类型安全的补丁
为什么参数化类型的数组不是类型安全的
未经处理的类型和不被检查的警告
即使被重写的java集合类带来了泛型的好处,在使用他们的时候您也不被要求说明类型变量。一个不带类型变量的泛型类型被认为是一个未经处理的类型(raw type)。这样,5.0版本以前的jav
闽公网安备 35060202000074号