分类: Java
2007-05-20 11:15:03
Java1.5泛型指南中文版(Java1.5 Generic Tutorial)
英文版pdf下载链接:
译者:
目 录
(第四部分)
直到现在,我们的例子中都假定了一个理想的世界,那里所有人使用的都是最新版本的java编程语言,它支持泛型。
唉,现实并非如此。百万行代码都是在早先版本的语言下写作的,他们不可能一晚上就转换过来。
后面,在第10部分,我们会解决把老代码转换为使用泛型的代码的问题。在这里,我们把注意力放在一个更简单的问题:老代码怎么和泛型代码交互?这个问题包括两部分:在泛型中使用老代码和在老代码中使用泛型代码。
怎样才能使用老代码的同时在自己的代码中享受泛型带来的好处?
作为一个例子,假定你像使用包 com.Fooblibar.widgets。Fooblibar.com(完全虚构出来的公司) 的人们出售一种进行库存管理的系统,下面是主要代码:
package com.Fooblibar.widgets;
public interface Part { ...}
public class Inventory {
/**
* 添加一个新配件到库存数据库
* 配件有名字name, 并由零件(Part)的集合组成。
* 零件由parts 指定. collection parts 中的元素必须实现Part接口。
**/
public static void addAssembly(String name, Collection parts) {...} public static Assembly getAssembly(String name) {...}
}
public interface Assembly {
Collection getParts(); // Returns a collection of Parts
}
现在,你想使用上述API写新代码。如果能保证调用addAssembly()时总是使用正确的参数会很棒——就是说,你传进去的确实时一个Part的Collection。当然,泛型可以实现这个目的:
package com.mycompany.inventory;
import com.Fooblibar.widgets.*;
public class Blade implements Part {
...
}
public class Guillotine implements Part {
}
public class
public static void main(String[] args) {
Collection<Part> c = new ArrayList<Part>();
c.add(new Guillotine()) ;
c.add(new Blade());
Inventory.addAssembly(”thingee”, c);
Collection<Part> k = Inventory.getAssembly(”thingee”).getParts();
}
}
当我们调用addAssembly,它希望第二个参数是Collection类型。而实际参数是Collection
在严格的泛型代码里,Collection应该总是带着类型参数。当一个泛型类型,比如Collection被使用而没有类型参数时,它被称作一个raw type(自然类型??)。
大多数人的第一直觉时Collection实际上意味着 Collection。但是,像我们前面看到的,当需要Collection时传递 Collection
但是等一下,那也不正确。考虑getParts()这个调用,它返回一个Collection。然后它被赋值给k,而k是Collection
事实上,这个赋值是合法的,但是它产生一个未检查警告(unchecked warning)。这个警告是必要的,因为事实是编译器无法保证其正确性。我们没有办法检查getAssembly()中的旧代码来保证返回的确实是一个Collection
那么,这应该是一个错误么?理论上讲,Yes,但是实际上讲,如果泛型代码要调用旧代码,那么这必须被允许。这取决于你,程序员,在这种情况下来满足你自己。这个赋值是合法的因为getAssembly()的调用约定中说它返回一个Part的集合,即使这个类型声明中没有显示出这一点。
因此,自然类型和通配符类型很像,但是他们的类型检查不是同样严格。允许泛型与已经存在的老代码相交互是一个深思熟虑的决定。
从泛型代码中调用老代码具有先天的危险性,一旦你把泛型编程和非泛型编程混合起来,泛型系统所提供的所有安全保证都失效。然而,你还是比你根本不用泛型要好。至少你知道你这一端的代码是稳定的。
在非泛型代码远比泛型代码多的时候,不可避免会出现两者必须混合的情况。
如果你发现你不得不混合旧代码和泛型代码,仔细注意未检查警告(unchecked warnings),仔细考虑你怎样才能证明出现警告的部分代码是正确的。
如果你仍然犯了错,而导致警告的代码确实不是类型安全的,那么会发生什么?让我们看一下这种情形。在这个过程中,我们将了解一些编译器工作的内幕。
public String loophole(Integer x) {
List
List xs = ys;
xs.add(x); // compile-time unchecked warning
return ys.iterator().next();
}
这里,我们用一个老的普通的list的引用来指向一个String的list。我们插入一个Integer到这个list中,并且试图得到一个String。这是明显的错误。如果我们忽略这个警告并且试图运行以上代码,它将在我们试图使用错误的类型的地方失败。在运行的时候,上面的代码与下面的代码的行为一样:
public String loophole(Integer x) {
List ys = new LinkedList();
List xs = ys;
xs.add(x);
return (String) ys.iterator().next(); // run time error
}
当我们从list中获取一个元素的时候,并且试图通过转换为String而把它当作一个string,我们得到一个 ClassCastException。完全一样的事情发生在使用泛型的代码上。
这样的原因是,泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的loophole()转换成非泛型版本。
结果是,java虚拟机的类型安全和稳定性决不能冒险,即使在又unchecked warning的情况下。
(原文:As a result, the type safety and integrity of the Java virtual machine are never at risk, even in the presence of unchecked warnings.)
基本上,擦除去掉了所有的泛型类型信息。所有在尖括号之间的类型信息都被扔掉了,因此,比如说一个List
擦除的全部的细节超出了本文的范围,但是我们给出的简单描述与事实很接近。知道一点这个有好处,特别是如果你要作一些复杂的事,比如把现有API转换成使用泛型的代码(第10部分)或者仅仅是想理解为什么会这样。
现在让我们来考虑相反的情形。假定Fooblibar.com公司的人决定把他们的代码转换为使用泛型来实现,但是他们的一些客户没有转换。现在代码就像下面:
package com.Fooblibar.widgets;
public interface Part { ...}
public class Inventory {
/**
* Adds a new Assembly to the inventory database.
* The assembly is given the name name, and consists of a set
* parts specified by parts. All elements of the collection parts
* must support the Part interface.
**/
public static void addAssembly(String name, Collection<Part> parts) {...}
public static Assembly getAssembly(String name) {...}
}
public interface Assembly {
Collection<Part> getParts(); // Returns a collection of Parts
}
客户端代码如下:
package com.mycompany.inventory;
import com.Fooblibar.widgets.*;
public class Blade implements Part {
...
}
public class Guillotine implements Part {
}
public class
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(new Guillotine()) ;
c.add(new Blade());
Inventory.addAssembly(”thingee”, c); // 1: unchecked warning
Collection k = Inventory.getAssembly(”thingee”).getParts();
}
}
客户端代码是在泛型被引入之前完成的,但是它使用了包com.Fooblibar.widgets和集合库,它们都使用了泛型。客户端代码中的泛型类的声明都是使用了自然类型(raw types)。第1行产生一个unchecked warning,因为一个自然的Collection被传递到一个需要Collection
你还有另一种选择,你可以使用source 1.4 标志来编译客户端代码,以保证不会产生警告。但是这种情况下你无法使用jdk1.5 中的任何新特性。