本站首页    管理页面    写新日志    退出


«September 2025»
123456
78910111213
14151617181920
21222324252627
282930


公告
 本博客在此声明所有文章均为转摘,只做资料收集使用。

我的分类(专题)

日志更新

最新评论

留言板

链接

Blog信息
blog名称:
日志总数:1304
评论数量:2242
留言数量:5
访问次数:7609185
建立时间:2006年5月29日




[J2SE]generic-泛型/类属(二)
软件技术

lhwork 发表于 2006/8/21 10:00:17

管中窥虎 在学习 java 1.5 的过程中,我使用了 sun 公布的 tutorial ,这份文档写的比较详尽易明,但是对于想快速了解 tiger 而且具有较好 java 基础的人来说,大篇幅的英文文档是比较耗时间和非必需的,所以我将会归纳这份文档的主要内容,在保证理解的底线上,尽力减少阅读者需要的时间。   在以下地址可以进入各新增语言特色介绍以及下载相关文档(若有)。 http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html   这一篇是接着上文继续的,在这里补充说明,虽然我希望以双语写作,但是把英文文档翻译过来后再翻译回去,似乎是件好傻的事情。。。所以这些翻译并精简的文章算是个例外吧。 第一道虎纹: generic -泛型 / 类属(二) 泛型方法 假设我们想把一个数组的元素都放到一个容器类里,下面是第一次尝试: 500)this.width=500'> 500)this.width=500'> static   void  fromArrayToCollection(Object[] a, Collection  <   ?   >  c)  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>500)this.width=500'>   for  (Object o : a)  500)this.width=500'> {          c.add(o);  //  compile time error  500)this.width=500'> 500)this.width=500'>  } 500)this.width=500'>}  500)this.width=500'> 现在你应该学会了不去犯初学者的错误,用 Collection < Object > 来作为参数,你也许也发现了用 Collection < ? > 也不成,不知道就是不知道,不能放东西进去。 好了,主角登场:泛型方法 500)this.width=500'> 500)this.width=500'> static   <  T  >   void  fromArrayToCollection(T[] a, Collection  <  T  >  c)  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>500)this.width=500'>   for  (T o : a)  500)this.width=500'> {          c.add(o);  //  correct  500)this.width=500'> 500)this.width=500'>    } 500)this.width=500'>}  500)this.width=500'> 方法的声明加入了类型参数,在上面这个方法里,如果 c 的元素类型是 a 的元素类型的父类,就能成功执行方法。下面这些代码可以帮助你了解一下: 500)this.width=500'> Object[] oa  =   new  Object[ 100 ]; 500)this.width=500'>500)this.width=500'>Collection  <  Object  >  co  =   new  ArrayList  <  Object  >  (); 500)this.width=500'>500)this.width=500'>fromArrayToCollection(oa, co);  //  T inferred to be Object  500)this.width=500'> 500)this.width=500'>String[] sa  =   new  String[ 100 ]; 500)this.width=500'>500)this.width=500'>Collection  <  String  >  cs  =   new  ArrayList  <  String  >  (); 500)this.width=500'>500)this.width=500'>fromArrayToCollection(sa, cs);  //  T inferred to be String  500)this.width=500'> 500)this.width=500'>fromArrayToCollection(sa, co);  //  T inferred to be Object  500)this.width=500'> 500)this.width=500'>Integer[] ia  =   new  Integer[ 100 ]; 500)this.width=500'>500)this.width=500'>Float[] fa  =   new  Float[ 100 ]; 500)this.width=500'>500)this.width=500'>Number[] na  =   new  Number[ 100 ]; 500)this.width=500'>500)this.width=500'>Collection  <  Number  >  cn  =   new  ArrayList  <  Number  >  (); 500)this.width=500'>500)this.width=500'>fromArrayToCollection(ia, cn);  //  T inferred to be Number  500)this.width=500'> 500)this.width=500'>fromArrayToCollection(fa, cn);  //  T inferred to be Number  500)this.width=500'> 500)this.width=500'>fromArrayToCollection(na, cn);  //  T inferred to be Number  500)this.width=500'> 500)this.width=500'>fromArrayToCollection(na, co);  //  T inferred to be Object  500)this.width=500'> 500)this.width=500'>fromArrayToCollection(na, cs);  //  compile-time error  500)this.width=500'> 500)this.width=500'>  500)this.width=500'> 注意到我们并没有真正的传入一个类型实参,而是由编译器以方法的实际参数对象来推断,它推断出使得这次方法调用成立的类型,并尽可能地特化这个类型。比如说如果 T 推断为 Number 依然成立的时候,就不会推断为 Object 。 现在看来,泛型方法和通配符有些共通的地方,使得类属有一定的灵活性。那么什么时候用泛型方法,什么时候用通配符?看看下面的例子: 500)this.width=500'> interface Collection < E > 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>   public boolean containsAll(Collection < ? > c); 500)this.width=500'>500)this.width=500'>   public boolean addAll(Collection < ? extends E > c); 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 以及: 500)this.width=500'> interface Collection < E > 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>   public < T > boolean containsAll(Collection < T > c); 500)this.width=500'>500)this.width=500'>   public < T extends E > boolean addAll(Collection < T > c); 500)this.width=500'>500)this.width=500'>   // 注意类型变量也可以有上限哦~ 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 这两种方式都达成了同样的目的,使得方法有了多态性。然而注意到在每个方法的声明中, T 只出现了一次,这种情况下就应该用通配符,通配符的主要目的就是提供弹性的泛化,而多态方法则用于表达两个或多个参数间的依赖关系,你回过头去看多态方法的第一个例子,是不是这个情况?如果不存在依赖关系需要表达,就不应该用多态方法,因为从可读性上来说,通配符更清晰。   而且有趣的是,它们并非水火不容,反而可以精妙配合,如下: 500)this.width=500'> class Collections 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>500)this.width=500'>  public static < T > void copy(List < T > dest, List < ? extends T > src) 500)this.width=500'>{ 500)this.width=500'> } 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 这个合作使得 dest 与 src 的依赖关系得以表达,同时让 src 的接纳范畴扩大了。假如我们只用泛型方法来实现: 500)this.width=500'> class Collections 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>500)this.width=500'>   public static < T, S extends T > void copy(List < T > dest, List < S > src) 500)this.width=500'>{ 500)this.width=500'> } 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 那么 S 的存在就显得有些不必要,有些不优雅。总的来说,通配符更简洁清晰,只要情况允许就应该首选。   下面是给几何图形家族添加了一个有记忆功能的绘图方法,展示了泛型方法的使用,有兴趣就看看,略过也不影响下一步的学习。   500)this.width=500'> static  List  <  List  <   ?   extends  Shape  >>  history  =   new  ArrayList  <  List  <   ?   extends  Shape  >>  (); 500)this.width=500'>500)this.width=500'>500)this.width=500'>    public   void  drawAll(List  <   ?   extends  Shape  >  shapes)  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>           history.addLast(shapes); 500)this.width=500'>500)this.width=500'>500)this.width=500'>       for (Shape s: shapes) 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>           s.draw( this ); 500)this.width=500'>500)this.width=500'>          } 500)this.width=500'>500)this.width=500'>}  500)this.width=500'>500)this.width=500'>  500)this.width=500'>   这里又再谈谈命名规范的事情,用 T 来表示类型( type )就是个很好的选择,假如已经没有更多的背景信息的话,而在泛型方法里就是这样子,我们只是想表达一个类型。那么如果有多个参数出现,那么用 T 的街坊邻里就不错, S 啊,什么的。如果方法出现在一个泛型类里,就主要避免类的泛型变量和方法的泛型变量同名混淆,同样的,泛型类的嵌套泛型类也应该注意。   和遗老们打交道 很显然的,这个星球上存在的 java 代码里,没有引入泛型的还是多数,它们也不会一夜走进新社会,怎么把它们转换为泛型的会在晚些再谈及,现在我们谈谈和它们打交道的事情。这包括两方面:在引入泛型的代码里使用老代码,在老代码上使用引入了泛型的代码。   首先看看前者,看例子: 500)this.width=500'> 500)this.width=500'> public   interface  Part  500)this.width=500'> { 500)this.width=500'> }  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   class  Inventory  500)this.width=500'> {  /** */ /**  500)this.width=500'>500)this.width=500'>* Adds a new Assembly to the inventory database. 500)this.width=500'>500)this.width=500'>* The assembly is given the name name, and consists of a set 500)this.width=500'>500)this.width=500'>* parts specified by parts. All elements of the collection parts 500)this.width=500'>500)this.width=500'>* must support the Part interface. 500)this.width=500'>500)this.width=500'>* */  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   static   void  addAssembly(String name, Collection parts)  500)this.width=500'> { 500)this.width=500'> }  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   static  Assembly getAssembly(String name)  500)this.width=500'> { 500)this.width=500'> }  500)this.width=500'>500)this.width=500'>}  500)this.width=500'>500)this.width=500'> public   interface  Assembly  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>   Collection getParts();  //  Returns a collection of Parts  500)this.width=500'> 500)this.width=500'>}  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   class  Blade  implements  Part 500)this.width=500'>  500)this.width=500'> {}  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   class  Guillotine  implements  Part  500)this.width=500'> {}  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   class  Main  500)this.width=500'> { 500)this.width=500'>500)this.width=500'> public static void main(String[] args) 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>   Collection < Part > c = new ArrayList < Part > (); 500)this.width=500'>500)this.width=500'>   c.add(new Guillotine()) ; 500)this.width=500'>500)this.width=500'>   c.add(new Blade()); 500)this.width=500'>500)this.width=500'>   Inventory.addAssembly(”thingee”, c); 500)this.width=500'>500)this.width=500'>   Collection < Part > k = Inventory.getAssembly(”thingee”).getParts();// 琢磨一下这里? 500)this.width=500'>500)this.width=500'>} 500)this.width=500'>500)this.width=500'>}  500)this.width=500'>500)this.width=500'> 上面的代码有没有问题?如果前面的内容里你没打瞌睡,你应该发现注解处的那个语句很有问题,类型不安全问题。我们称Collection这种不带类型参数的使用叫做原始类型,编译器无法保证这样子的容器类放了什么东西,但是编译器会以不那么严格的标准去要求这个旧社会的人,否则,老代码将完全不能在1.5里使用,编译器会发出一个unchecked warning,怎么处理由你来决定。这样的设计是符合实际的,否则就是和已有代码彻底决裂。 虽然这样的调用会有错误的风险,但总比你完全不用泛型机制好,因为至少你保证了在你的这一端的类型安全,而且总有一天,英特那雄耐尔一定会实现。。。。 J 严肃地回到我们的话题,既然有风险,那么当你得到了这样的警告时,小心检查则是目前最好的对策。 但是,如果你不理会一个这样的警告,而且事实上你真的犯了一个类型安全的错误,会发生什么呢? 消除与翻译   500)this.width=500'> public String loophole(Integer x) 500)this.width=500'>{ List < String > ys = new LinkedList < String > (); 500)this.width=500'>500)this.width=500'>   List xs = ys; 500)this.width=500'>500)this.width=500'>      xs.add(x); // compile-time unchecked warning 500)this.width=500'>500)this.width=500'>      return ys.iterator().next(); 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 这个警告所在的地方确实是有问题的,一个 Integer 被放入了一个 List 中,这个 List 只是原始类型,所以编译器只能给个警告,但事实上它又是指向了 ys 指向的对象,一个只放 String 的 List ,在最后一句里,会发生什么事情?实际运行起来,这段代码就等同于: 500)this.width=500'> public String loophole(Integer x) 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>   List ys = new LinkedList; 500)this.width=500'>500)this.width=500'>   List xs = ys; 500)this.width=500'>500)this.width=500'>   xs.add(x); 500)this.width=500'>500)this.width=500'>   return (String) ys.iterator().next(); // run time error 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 它们会得到同样的错误:一个 ClassCastException 。 为什么呢?因为泛型在 java 编译器里是以一种称为“消除”的前端转换实现的,你几乎,我说几乎,可以认为是一种代码到代码的翻译,象上面这样,把带泛型的版本翻译成不带泛型的版本,接下来,当代码的执行交到 JVM 的手里时,它可不管你是哪朝代的人,有错就是有错,类型安全的基本政策不动摇,即使你手里拽着 unchecked warnings 的证明。 基本上,消除机制就是把类型信息都扔掉了, List<String> 变成了 List ,上限通常都变成了 Object ,而且,当转换后的代码不符合泛型里的类型限制时,就添加一个类型转换,就是上例中那个 String 的转换会出现的原因。 消除机制的细节不是这里讨论的内容,这里简单的让你了解一些需要了解的情况而已。     假如你的代码更新为: 500)this.width=500'> public String loophole(Integer x) 500)this.width=500'>{ 500)this.width=500'>500)this.width=500'>   List ys = new LinkedList; 500)this.width=500'>500)this.width=500'>   List xs = ys; 500)this.width=500'>500)this.width=500'>   xs.add(x); 500)this.width=500'>500)this.width=500'>    return (String) ys.iterator().next(); // run time error 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 而调用这个代码的老客户代码是: 500)this.width=500'> 500)this.width=500'> public   class  Blade  implements  Part 500)this.width=500'>  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>}  500)this.width=500'>500)this.width=500'>500)this.width=500'> public   class  Guillotine  implements  Part  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>}  500)this.width=500'>500)this.width=500'> public   class  Main  500)this.width=500'> {  public   static   void  main(String[] args)  500)this.width=500'> { 500)this.width=500'>500)this.width=500'>   Collection c  =   new  ArrayList(); 500)this.width=500'>500)this.width=500'>   c.add( new  Guillotine()) ; 500)this.width=500'>500)this.width=500'>   c.add( new  Blade()); 500)this.width=500'>500)this.width=500'>   Inventory.addAssembly(”thingee”, c);  //  1: unchecked warning  500)this.width=500'> 500)this.width=500'>   Collection k  =  Inventory.getAssembly(”thingee”).getParts(); 500)this.width=500'>500)this.width=500'>} 500)this.width=500'> 500)this.width=500'>     也就是在老代码里调用泛型化了的代码。 如注解 1 所示,会有警告出现,原因你也已经了解了。你可以在编译时选择用 1.4 的环境,那么就不会有警告出现,但同样你也失去了 1.5 带来的各种新特色了。   这一篇到此为止,下一篇将看看一些还需要补充说明的地方,但并不意味着内容会很简单。


阅读全文(1659) | 回复(0) | 编辑 | 精华
 



发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.396 second(s), page refreshed 144766646 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号