Chinaunix首页 | 论坛 | 博客
  • 博客访问: 405893
  • 博文数量: 38
  • 博客积分: 1490
  • 博客等级: 上尉
  • 技术积分: 406
  • 用 户 组: 普通用户
  • 注册时间: 2006-01-08 00:52
文章分类

全部博文(38)

文章存档

2014年(1)

2013年(1)

2008年(6)

2007年(7)

2006年(23)

我的朋友

分类: Java

2007-05-20 11:15:03

Java1.5泛型指南中文版(Java1.5 Generic Tutorial)

英文版pdf下载链接:

                                      译者:  

       

(第四部分) 

 原文地址http://blog.csdn.net/explorers/archive/2005/08/15/454837.aspx

直到现在,我们的例子中都假定了一个理想的世界,那里所有人使用的都是最新版本的java编程语言,它支持泛型。

唉,现实并非如此。百万行代码都是在早先版本的语言下写作的,他们不可能一晚上就转换过来。

后面,在第10部分,我们会解决把老代码转换为使用泛型的代码的问题。在这里,我们把注意力放在一个更简单的问题:老代码怎么和泛型代码交互?这个问题包括两部分:在泛型中使用老代码和在老代码中使用泛型代码。

怎样才能使用老代码的同时在自己的代码中享受泛型带来的好处?

作为一个例子,假定你像使用包 com.Fooblibar.widgetsFooblibar.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()时总是使用正确的参数会很棒——就是说,你传进去的确实时一个PartCollection。当然,泛型可以实现这个目的:

package com.mycompany.inventory;

import com.Fooblibar.widgets.*;

public class Blade implements Part {

...

}

public class Guillotine implements Part {

}

public class Main {

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 类型。这可以工作,但是为什么?毕竟,大多数集合不包含Part对象,而且总的来说,编译器无法知道Collection指的是什么类型的集合。

在严格的泛型代码里,Collection应该总是带着类型参数。当一个泛型类型,比如Collection被使用而没有类型参数时,它被称作一个raw type(自然类型??)

大多数人的第一直觉时Collection实际上意味着 Collection。但是,像我们前面看到的,当需要Collection时传递 Collection是不安全的。类型Collection表示一个未知类型元素的集合,就像Collection,这样说更准确。

但是等一下,那也不正确。考虑getParts()这个调用,它返回一个Collection。然后它被赋值给k,而kCollection。如果这个调用的结果是一个Collection,这个赋值应该是一个错误。

事实上,这个赋值是合法的,但是它产生一个未检查警告(unchecked warning)。这个警告是必要的,因为事实是编译器无法保证其正确性。我们没有办法检查getAssembly()中的旧代码来保证返回的确实是一个Collection。代码里使用的类型是Collection,可以合法的向其中加入任何Object

那么,这应该是一个错误么?理论上讲,Yes,但是实际上讲,如果泛型代码要调用旧代码,那么这必须被允许。这取决于你,程序员,在这种情况下来满足你自己。这个赋值是合法的因为getAssembly()的调用约定中说它返回一个Part的集合,即使这个类型声明中没有显示出这一点。

因此,自然类型和通配符类型很像,但是他们的类型检查不是同样严格。允许泛型与已经存在的老代码相交互是一个深思熟虑的决定。

从泛型代码中调用老代码具有先天的危险性,一旦你把泛型编程和非泛型编程混合起来,泛型系统所提供的所有安全保证都失效。然而,你还是比你根本不用泛型要好。至少你知道你这一端的代码是稳定的。

在非泛型代码远比泛型代码多的时候,不可避免会出现两者必须混合的情况。

如果你发现你不得不混合旧代码和泛型代码,仔细注意未检查警告(unchecked warnings)仔细考虑你怎样才能证明出现警告的部分代码是正确的。

如果你仍然犯了错,而导致警告的代码确实不是类型安全的,那么会发生什么?让我们看一下这种情形。在这个过程中,我们将了解一些编译器工作的内幕。

 

(Erasure and Translation)

public String loophole(Integer x) {

       List ys = new LinkedList>();

       List xs = ys;

       xs.add(x); // compile-time unchecked warning

       return ys.iterator().next();

}

这里,我们用一个老的普通的list的引用来指向一个Stringlist。我们插入一个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类型被转换为List。所有对类型变量的引用被替换成类型变量的上限(通常是Object)。而且,无论何时如果结果代码类型不正确,会插入一个到合适的类型的转换,就像loophole的最后一行那样。

擦除的全部的细节超出了本文的范围,但是我们给出的简单描述与事实很接近。知道一点这个有好处,特别是如果你要作一些复杂的事,比如把现有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 Main {

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的地方,而编译器无法保证Collection就是一个Collection

你还有另一种选择,你可以使用source 1.4 标志来编译客户端代码,以保证不会产生警告。但是这种情况下你无法使用jdk1.5 中的任何新特性。

<未完...请看第五部分>

阅读(2393) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~