编辑按《Java in a Nutshell th Edition》覆盖了jdk中很多变化和新特征其中最重要的就是泛型在本文的第一部分作者David Flanagan介绍了如何使用泛型而在第二部分作者描述了如何写你自己的泛型和泛型方法
Java的新特性之一是引入了泛型类型和泛型方法一个泛型类型通过使用一个或多个类型变量来定义并拥有一个或多个使用一个类型变量作为一个参数或者返回值的占位符例如类型javautilList<E>是一个泛型类型一个list其元素的类型被占位符E描述这个类型有一个名为add()的方法被声明为有一个类型为E的参数同时有一个get()方法返回值被声明为E类型
为了使用泛型类型你应该为类型变量详细指明实际的类型形成一个就像List<String>类似的参数化类型[]指明这些额外的类型信息的原因是编译器据此能够在编译期为您提供很强的类型检查增强您的程序的类型安全性举个例子来说您有一个只能保持String对象的List那么这种类型检查就能够阻止您往里面加入String[]对象同样的增加的类型信息使编译器能够为您做一些类型转换的事情比如编译器知道了一个List<String>有个get()方法其返回值是一个String对象因此您不再需要去将返回值由一个Object强制转换为String
Javautil包中的集合类在java中已经被做成了泛型也许您将会在您的程序中频繁的使用到他们类型安全的集合类就是一个泛型类型的典型案例即便您从没有定义过您自己的泛型类型甚至从未用过除了javautil中的集合类以外的泛型类型类型安全的集合类的好处也是极有意义的一个标志——他们证明了这个主要的新语言特性的复杂性
我们从探索类型安全的集合类中的基本的泛型用法开始进而研究更多使用泛型类型的复杂细节然后我们讨论类型参数通配符和有界通配符描绘了如何使用泛型以后我们阐明如何编写自己的泛型类型和泛型方法我们对于泛型的讨论将结束于一趟对于JavaAPI的核心中重要的泛型类型的旅行这趟旅程将探索这些类型以及他们的用法旅程的目的是为了让您对泛型如何工作这个问题有个深入的理解
类型安全集合类
Javautil类包包含了Java集合框架(Java Collections Framework)这是一批包含对象的set对象的list以及基于keyvalue的map第五章将谈到集合类这里我们讨论的是在java中集合类使用类型参数来界定集合中的对象的类型这个讨论并不适合java或更早期版本如果没有泛型对于集合类的使用需要程序员记住每个集合中元素的类型当您在java种创建了一个集合您知道您放入到集合中的对象的类型但是编译器不知道您必须小心地往其中加入一个合适类型的元素当需要从集合中获取元素时您必须显式的写强制类型转换以将他们从Object转换为他们真是的类型考察下边的java的代码
public static void main(String[] args) {// This list is intended to hold only strings// The compiler doesnt know that so we have to remember ourselvesList wordlist = new ArrayList();// Oops! We added a String[] instead of a String// The compiler doesnt know that this is an errorwordlistadd(args);// Since List can hold arbitrary objects the get() method returns// ObjectSince the list is intended to hold strings we cast the// return value to String but get a ClassCastException because of// the error aboveString word = (String)wordlistget();}
泛型类型解决了这段代码中的显示的类型安全问题Javautil中的List或是其他集合类已经使用泛型重写过了就像前面提到的 List被重新定义为一个list它中间的元素类型被一个类型可变的名称为E的占位符描述Add()方法被重新定义为期望一个类型为E的参数用于替换以前的Objectget()方法被重新定义为返回一个E替换了以前的Object
在java中当我们申明一个List或者创建一个ArrayList的实例的时候我们需要在泛型类型的名字后面紧跟一对<>尖括号中写入我们需要的实际的类型比如一个保持String的List应该写成List<String>需要注意的是这非常象给一个方法传一个参数区别是我们使用类型而不是值同时使用尖括号而不是圆括号
Javautil的集合类中的元素必须是对象化的他们不能是基本类型泛型的引入并没有改变这点泛型不能使用基本类型我们不能这样来申明——Set<char>或者List<int>记住无论如何java中的自动打包和自动解包特性使得使用Set<Character>或者List<Integer>和直接使用char和int值一样方便(查看第二章以了解更多关于自动打包和自动解包的细节)
在Java中上面的例子将被重写为如下方式
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 wont let us do thiswordlistadd(args);// Compilation error!// We can do this though// Notice the use of the new for/in looping statementfor(String arg : args) wordlistadd(arg);// No cast is requiredList<String>get() returns a StringString word = wordlistget();}
值得注意的是代码量其实并没有比原来那个没有泛型的例子少多少使用(String)这样的类型转换被替换成了类型参数<String> 不同的是类型参数需要且仅需要声明一次而list能够被使用任何多次不需要类型转换在更长点的例子代码中这一点将更加明显即使在那些看上去泛型语法比非泛型语法要冗长的例子里使用泛型依然是非常有价值的——额外的类型信息允许编译器在您的代码里执行更强的错误检查以前只能在运行起才能发现的错误现在能够在编译时就被发现此外以前为了处理类型转换的异常我们需要添加额外的代码行如果没有泛型那么当发生类型转换异常的时候一个ClassCastException异常就会被从实际代码中抛出
就像一个方法可以使用任意数量的参数一样类允许使用多个类型变量接口JavautilMap就是一个例子一个Map体现了从一个key的对象到一个value的对象的映射关系接口Map申明了一个类型变量来描述key的类型而另一个类型变量来描述value的类型举个例子来说假设您希望做一个String对象到Integer对象的映射关系
public static void main(String[] args) {// A map from strings to their position in the args[] arrayMap<StringInteger> map = new HashMap<StringInteger>();// Note that we use autoboxing to wrap i in an Integer objectfor(int i=; i < argslength; i++) mapput(args[i] i);// Find the array index of a wordNote no cast is required!Integer position = mapget(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 mapint pos = mapget(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 castingWe might still get NullPointerException// or ArrayIndexOutOfBounds at runtime of courseint value = mapget(key)get()get()[];// Heres how we break that expression down step by stepList<List<int[]>> listOfLists = mapget(key);List<int[]> listOfIntArrays = listOfListsget();int[] array = listOfIntArraysget();int element = array[];
在上面的代码里javautilList<E>和javautilMap<KV>的get()方法返回一个类型为E的list元素或者一个类型为V的map元素注意无论如何泛型类型能够更精密的使用他们的变量在本书中的参考章节查看List<E>您将会看到它的iterator( )方法被声明为返回一个Iterator<E>这意味着这个方法返回一个跟list的实际的参数类型一样的一个参数类型的实例为了具体的说明这点下面的例子提供了不使用get()方法来获取一个List<String>的第一个元素的方法
List<String> words = // initialized elsewhereIterator<String> iterator = erator();String firstword = iteratornext();
理解泛型类型
本段将对泛型类型的使用细节做进一步的探讨以尝试说明下列问题
不带类型参数的使用泛型的后果
参数化类型的体系
一个关于编译期泛型类型的类型安全的漏洞和一个用于确保运行期类型安全的补丁
为什么参数化类型的数组不是类型安全的
未经处理的类型和不被检查的警告
即使被重写的Java集合类带来了泛型的好处在使用他们的时候您也不被要求说明类型变量一个不带类型变量的泛型类型被认为是一个未经处理的类型(raw type)这样版本以前的jav