摘要
泛型是j2se 5.0最重要的特性。他们让你写一个type(类或接口)和创建一个实例通过传递一个或多个引用类型。这个实例受限于只能作用于这些类型。比如,在java 5,java.util.list 已经被泛化。当建立一个list对象时,你通过传递一个java类型建立一个list实例,此list实例只能作用于所传递的类型。这意味着如果你传递一个string ,此list实例只能拥有string对象;如果你传递一个integer,此实例只能存贮integer对象。除了创建参数化的类型,你还能创建参数化的函数。
泛型的第一个好处是编译时的严格类型检查。这是集合框架最重要的特点。此外,泛型消除了绝大多数的类型转换。在jdk 5.0之前,当你使用集合框架时,你不得不进行类型转换。
本文将教你如何操作泛型。它的第一部分是“没有泛型的日子”,先让我们回忆老版本jdk的不便。然后,举一些泛型的例子。在讨论完语法以及有界泛型的使用之后,文章最后一章将解释如何写泛型。
没有泛型的日子
所有的java类都源自java.lang.object,这意味着所有的java对象能转换成object。因此,在之前的jdk的版本中,很多集合框架的函数接受一个object参数。所以,collections是一个能持有任何对象的多用途工具,但带来了不良的后果。
举个简单的例子,在jdk 5.0的之前版本中,类list的函数add接受一个object参数:
public boolean add(java.lang.object element)
所以你能传递任何类型给add。这是故意这么设计的。否则,它只能传递某种特定的对象,这样就会出现各种list类型,如,stringlist, employeelist, addresslist等。
add通过object传递能带来好处,现在我们考虑get函数(返回list中的一个元素).如下是jdk 5之前版本的定义:
public java.lang.object get(int index) throws indexoutofboundsexception
get返回一个object.不幸的事情从此开始了.假如你储存了两个string对象在一个list中:
list stringlist1 = new arraylist();stringlist1.add("java 5");stringlist1.add("with generics");
当你想从stringlist1取得一个元素时,你得到了一个object.为了操作原来的类型元素,你不得不把它转换为string。
string s1 = (string) stringlist1.get(0);
但是,假如你曾经把一个非string对象加入stringlist1中,上面的代码会抛出一个classcastexception. 有了泛型,你能创建一个单一用途的list实例.比如,你能创建一个只接受string对象的list实例,另外一个实例只能接受employee对象.这同样适用于集合框架中的其他类型.
泛型入门
像一个函数能接受参数一样,一个泛型也能接受参数.这就是一个泛型经常被称为一个参数化类型的原因.但是不像函数用()传递参数,泛型是用<>传递参数的.声明一个泛型和声明一个普通类没有什么区别,只不过你把泛型的变量放在<>中.
比如,在jdk 5中,你可以这样声明一个java.util.list : list<e> mylist;
e 称为类型变量.意味着一个变量将被一个类型替代.替代类型变量的值将被当作参数或返回类型.对于list接口来说,当一个实例被创建以后,e 将被当作一个add或别的函数的参数.e 也会使get或别的参数的返回值.下面是add和get的定义:
boolean add<e o>e get(int index)
note:一个泛型在声明或例示时允许你传递特定的类型变量: e.除此之外,如果e是个类,你可以传递子类;如果e是个接口,你可以传递实现接口的类;
list<number> numberlist= new arraylist<number>();
numberlist.add(2.0);
numberlist.add(2);
如果你传递一个string给一个list,比如:
list<string> mylist;
那么mylist的add函数将接受一个string作为他的参数,而get函数将返回一个string.因为返回了一个特定的类型,所以不用类型转化了。
note:根据惯例,我们使用一个唯一的大写字目表示一个类型变量。为了创建一个泛型,你需在声明时传递同样的参数列表。比如,你要想创建一个arraylist来操作string ,你必须把string放在<>中。如:
list<string> mylist = new arraylist<string>();
再比如,java.util.map 是这么定义的:
public interface map<k,v>
k用来声明map键(key)的类型而v用来表示值(value)的类型。put和values是这么定义的:
v put(k key, v value)collection<v> values()
note:一个泛型不准直接的或间接的是java.lang.throwable的子类。因为异常是在运行时抛出的,所以它不可能预言什么类型的异常将在编译时抛出.
列表1的例子将比较list在jdk 1.4 和jdk1.5的不同
package com.brainysoftware.jdk5.app16;import java.util.list;import java.util.arraylist;public class genericlisttest { public static void main(string[] args) { // in jdk 1.4 list stringlist1 = new arraylist(); stringlist1.add("java 1.0 - 5.0"); stringlist1.add("without generics"); // cast to java.lang.string string s1 = (string) stringlist1.get(0); system.out.println(s1.touppercase()); // now with generics in jdk 5 list<string> stringlist2 = new arraylist<string>(); stringlist2.add("java 5.0"); stringlist2.add("with generics"); // no need for type casting string s2 = stringlist2.get(0); system.out.println(s2.touppercase()); }}
在列表1中,stringlist2是个泛型。声明list<string>告诉编译器list的实例能接受一个string对象。当然,在另外的情况中,你能新建能接受各种对象的list实例。注意,当从list实例中返回成员元素时,不需要对象转化,因为他返回的了你想要的类型,也就是string.
note:泛型的类型检查(type checking)是在编译时完成的.
最让人感兴趣的事情是,一个泛型是个类型并且能被当作一个类型变量。比如,你想你的list储存lists of strings,你能通过把list<string>作为他的类型变量来声明list。比如:
list<list<string>> mylistoflistsofstrings;
要从mylist中的第一个list重新取得string,你可以这么用:
string s = mylistoflistsofstrings.get(0).get(0);
下一个列表中的listofliststest类示范了一个list(命名为listoflists)接受一个string list作为参数。
package com.brainysoftware.jdk5.app16;import java.util.arraylist;import java.util.list;public class listofliststest { public static void main(string[] args) { list<string> listofstrings = new arraylist<string>(); listofstrings.add("hello again"); list<list<string>> listoflists = new arraylist<list<string>>(); listoflists.add(listofstrings); string s = listoflists.get(0).get(0); system.out.println(s); // prints "hello again" }}
另外,一个泛型接受一个或多个类型变量。比如,java.util.map有两个类型变量s。第一个定义了键(key)的类型,第二个定义了值(value)的类型。下面的例子讲教我们如何使用个一个泛型map.
package com.brainysoftware.jdk5.app16;import java.util.hashmap;import java.util.map;public class maptest { public static void main(string[] args) { map<string, string> map = new hashmap<string, string>(); map.put("key1", "value1"); map.put("key2", "value2"); string value1 = map.get("key1"); }}
在这个例子中,重新得到一个key1代表的string值,我们不需要任何类型转换。
没有参数的情况下使用泛型
既然在j2se 5.0中收集类型已经泛型化,那么,原来的使用这些类型的代码将如何呢?很幸运,他们在java 5中将继续工作,因为你能使用没有参数的泛型。比如,你能继续像原来一样使用list接口,正如下面的例子一样。
list stringlist1 = new arraylist();stringlist1.add("java 1.0 - 5.0");stringlist1.add("without generics");string s1 = (string) stringlist1.get(0);
一个没有任何参数的泛型被称为原型(raw type)。它意味着这些为jdk1.4或更早的版本而写的代码将继续在java 5中工作。
尽管如此,一个需要注意的事情是,jdk5编译器希望你使用带参数的泛型。否则,编译器将提示警告,因为他认为你可能忘了定义类型变量s。比如,编译上面的代码的时候你会看到下面这些警告,因为第一个list被认为是原型。
note: com/brainysoftware/jdk5/app16/genericlisttest.java
uses unchecked or unsafe operations.
note: recompile with -xlint:unchecked for details.
当你使用原型时,如果你不想看到这些警告,你有几个选择来达到目的:
1.编译时带上参数-source 1.4
2.使用@supresswarnings("unchecked")注释
3.更新你的代码,使用list<object>. list<object>的实例能接受任何类型的对象,就像是一个原型list。然而,编译器不会报错。
使用 ? 通配符
前
闽公网安备 35060202000074号