ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 轻松掌握 Java 泛型 (第 3 部分)

轻松掌握 Java 泛型 (第 3 部分)

原创 Linux操作系统 作者:kirinri 时间:2008-01-10 18:02:54 0 删除 编辑

轻松掌握 Java 泛型 (第 3 部分)
来源:** 收集整理

 
 
 
 
 
  有效的构造函数调用

  首先,为了对类型参数
造函数对于 T 的每个实例
我们不知道 T 的某一实例

 构造合法的 new 表达式(如 ne
化都有效。但由于我们只知道 T
化将有什么构造函数。要解决这

 w T()),必须确保我们调用的构
是其已声明界限的子类型,所以
一问题,可以用下述三种方法之一

 

  要求类型参数的所有实例化都包括不带参数的(zeroary)构造函数。          
  只要泛型类的运行时实例化没有包括所需的构造函数,就抛出异常。                  
  修改语言的语法以包括更详尽的类型参数界限。                                    

  第 1 种方法:需要不带参数的构造函数

  只要求类型参数的所有
。使用这种方法也有先例。
 实例化都包括不带参数的构造函

 数。该解决方案的优点是非常简单

 

  处理类似问题的现有 J
函数来解决问题的。然而,
造函数。
 ava 技术(象 JavaBean 技术)
该方法的一个主要缺点是:对于

 就是通过要求一个不带参数的构造
许多类,没有合理的不带参数的构

 

  例如,表示非空容器的任何类在构造
构造函数将迫使我们先创建实例,然后再
实践会导致问题的产生(您可能想要阅读
Initializer bug pattern”,以获取详
 函数中必然使用表示其元素的参数。包括不带参数的
进行本来可以在构造函数调用中完成的初始化。但该
2002 年 4 月发表的本专栏文章“The Run-on
细信息;请参阅参考资料。)
 

  第 2 种方法:当缺少所需构造函数时,抛出异常

  处理该问题的另一种方法是:只要泛
异常。请注意:必须在运行时抛出异常。
态地确定所有将在运行时发生的泛型类的
 型类的运行时实例化没有包括所需构造函数,就抛出
因为 Java 语言的递增式编译模型,所以我们无法静
实例化。例如,假设我们有如下一组泛型类:
 

  清单 1.“裸”类型参数的 New 操作                                      

  class C< T> {

   T makeT() {                                                      
    return new T();                                            
   }                                                                          
  }                                                                            

  class D< S> {

   C< S> makeC() {

    return new C< S>();

   }                                                                          
  }                                                                            

  现在,在类 D< S> 中,构造了类 C< S> 的实例。然后,在类 C 的主体中,将调用 S 的不带参数的构造函数。这种不带参数的构造函数存在吗?答案当然取决于 S 的实例化!


  比方说,如果 S 被实例化为 String,那么答案是“存在”。如果它被实例化为 Integer,那么答案是“不存在”。但是,当编译类 D 和 C 时,我们不知道其它类会构造什么样的 D< S> 实例化。即使我们有可用于分析的整个程序(我们几乎从来没有这样的 Java 程序),我们还是必须进行代价相当高的流分析来确定潜在的构造函数问题可能会出现在哪里。 


  此外,这一技术所产生的错误种类对于程序员来说很难诊断和修复。例如,假设程序员只熟悉类 D 的头。他知道 D 的类型参数的界限是缺省界限(Object)。如果得到那样的信息,他没有理由相信满足声明类型界限(如 D< Integer>)的 D 的实例化将会导致错误。事实上,它在相当长的时间里都不会引起错误,直到最后有人调用方法 makeC 以及(最终)对 C 的实例化调用方法 makeT。然后,我们将得到一个报告的错误,但这将在实际问题发生很久以后 — 类 D 的糟糕实例化。


  还有,对所报告错误的
现在,让我们假设程序员无
绪,除非他设法联系类 C
 堆栈跟踪甚至可能不包括任何对
权访问类 C 的源代码。他对问
的维护者并获得线索。
 这个糟糕的 D 实例的方法调用!
题是什么或如何修正代码将毫无头

 

  第 3 种方法:修改语法以获得更详尽的界限

  另一种可能性是修改语言语法以包括
的构造函数,它们必须出现在参数的每一
的构造函数是那些在界限中声明的构造函
 更详尽的类型参数界限。这些界限可以指定一组可用
个实例化中。因而,在泛型类定义内部,唯一可调用
数。
 

  同样,实例化泛型类的
。参数声明将充当类与其客

 客户机类必须使用满足对构造函
户机之间的契约,这样我们可以

 数存在所声明的约束的类来这样做
静态地检查这两者是否遵守契约。

 

  与另外两种方法相比,
第一种方法中相同的静态检
 该方法有许多优点,它允许我们
查程度。但它也有需要克服的问
 保持第二种方法的可表达性以及与
题。
 

  首先,类型参数声明很
充的参数声明还过得去。另
必须确保这些扩充的声明将
 容易变得冗长。我们或许需要某
外,如果在 Tiger 以后的版本
与现有的已编译泛型类兼容。
 种形式的语法上的甜头,使这些扩
中添加扩充的参数声明,那么我们

 

  如果将对泛型类型的与类型相关的操
还不清楚。但是,从哪种方法将使 Java
能容易地修正)的观点看,第三个选项无
 作的支持添加到 Java 编程中,那么它采用何种形式
代码尽可能地保持健壮(以及使在它遭到破坏时尽可
疑是最适合的。
 

  然而,new 表达式有另一个更严重的问题。                                    

  多态递归

  更严重的问题是类定义中可能存在多
,发生多态递归。例如,考虑下面的错误
 态递归。当泛型类在其自己的主体中实例化其本身时
示例:
 

  清单 2. 自引用的泛型类                                                    

  class C< T> {

   public Object nest(int n) {                      
    if (n == 0) return this;                          
    else return new C< C< T>>().nest(n - 1);

   }                                                                          
  }                                                                            

  假设客户机类创建新的 C< Object> 实例,并调用(比方说)nest(1000)。然后,在执行方法 nest() 的过程中,将构造新的实例化 C< C< Object>>,并且对它调用 nest(999)。然后,将构造实例化 C< C< C< Object>>>,以此类推,直到构造 1000 个独立的类 C 的实例化。当然,我随便选择数字 1000;通常,我们无法知道在运行时哪些整数将被传递到方法 nest。事实上,可以将它们作为用户输入传入。 


  为什么这成为问题呢?
型相关的操作,那么,在程
入器为它所装入的每个类查
 因为如果我们通过为每个实例化
序运行以前,我们无法知道我们
找现有类文件,那么它会如何工
 构造独立类来支持泛型类型的与类
需要构造哪些类。但是,如果类装
作呢?
 

  同样,这里有几种可能的解决办法:                                             

  对程序可以产生的泛型类的实例化数目设置上限。                                  
  静态禁止多态递归。                                                            
  在程序运行时随需构造新的实例化类。                                            

  第 1 种:对实例化数设置上限

  我们对程序可以产生的
组合法的实例化确定有限界
 泛型类的实例化数目设置上限。
限,并且仅为该界限中的所有实
 然后,在编译期间,我们可以对一
例化生成类文件。
 

  该方法类似于在 C++ 标准模板库中
)。该方法的问题是,和为错误的构造函
一次运行将崩溃。例如,假设实例化数的
的 nest() 方法。那么,只要用户输入小
计划不周的设计就会失败。现在,设想一
码并试图弄清楚幻数 42 有什么特殊之处
 完成的事情(这使我们有理由担心它不是一个好方法
数调用报告错误一样,程序员将无法预知其程序的某
界限为 42,并且使用用户提供的参数调用先前提到
于 42 的数,一切都正常。当用户输入 43 时,这一
下可怜的代码维护者,他所面对的任务是重新组合代

 

  第 2 种:静态禁止多态递归

  为什么我们不向编译器
单就好了。)当然,包括我
的使用。
 发出类似“静态禁止多态递归”
在内的许多程序员都会反对这种

 这样的命令呢?(唉!要是那么简
策略,它抑制了许多重要设计模式

 

  例如,在泛型类 List< T> 中,您真的想要防止 List< List< T>> 的构造吗?从方法返回这种列表对于构建许多很常用的数据结构很有用。事实证明我们无法防止多态递归,即使我们想要那样,也是如此。就象静态检测糟糕的泛型构造函数调用一样,禁止多态递归会与递增式类编译发生冲突。我们先前的简单示例(其中,多态递归作为一个简单直接的自引用发生)会使这一事实变得模糊。但是,自引用对于在不同时间编译的大多数类常常采用任意的间接级别。再提一次,那是因为一个泛型类可以用其自己的类型参数来实例化另一个泛型类。 


  下面的示例涉及两个类之间的多态递归:                                          

  清单 3. 相互递归的多态递归                                                

  class C< T> {

   public Object potentialNest(int n) {    
    if (n == 0) return this;                          
    else return new D< T>().nest(n - 1);

   }                                                                          
  }                                                                            

  class D< S> {

   public Object nest(int n) {                      
    return new C< C< S>>().nest(n);

   }                                                                          
  }                                                                            

  在类 C 或 D 中显然没有多态递归,但象 new D< C< Object>>().nest(1000) 之类的表达式将引起类 C 的 1000 次实例化。


  或许,我们可以将新属
编译其它类时分析这些实例
观的错误消息。
 性添加到类文件中,以表明类中
化,以进行递归。但是,我们还

 所有不同泛型类型实例化,然后在
是必须向程序员提供奇怪的和不直

 

  在上面的代码中,我们在哪里报告错误呢?在类 D 的编译过程中还是在包含不相干表达式 new D< C< Object>>().nest(1000) 的客户机类的编译过程中呢?无论是哪一种,除非程序员有权访问类 C 的源代码,否则他无法预知何时会发生编译错误。 


  第 3 种:实时构造新的实例化类 

  另一种方法是在程序运行时按需构造
完全不兼容。但实际上,实现该策略所需
(template)”类文件构造新的实例化类
 新的实例化类。起先,这种方法似乎与 Java 运行时
的全部就是使用一个修改的类装入器,它根据“模板

 

  JVM 规范已经允许程序
Ant、JUnit 和 DrJava)
一起分布,以在较旧的 JVM
 员使用修改的类装入器;事实上
都使用它们。该方法的缺点是:
上运行。因为类装入器往往比
 ,许多流行的 Java 应用程序(如
修改的类装入器必须与其应用程序
较小,所以这个开销不会大。
 

  让我们研究一下该方法的工作示例。                                              

  NextGen 示例:修改的类装入器                                          
  前一种方法 — 用按需构造泛型类型
Java 语言的 NextGen 扩展所采用。修改
模板文件,不同的是这个模板文件在常量
些“洞”。非泛型类不受影响。
 实例化的修改的类装入器解决多态递归问题 — 被
的类装入器使用看上去几乎与普通类文件完全一样的
池中有一些“洞”,在装入时为每个实例化类填充这

 

  在 Rice 大学 JavaPLT
是 GJ 泛型 Java 编译器的
转换、instanceof 测试和
来支持多态递归。可以免费
 编程语言实验室,我们最近发
一种扩展,这种扩展支持类型参
new 表达式)。在该原型实现中
下载该原型(请参阅参考资料)
 布了 NextGen 编译器的原型,它
数的与类型相关的操作(数据类型
,我们使用了一个修改的类装入器

 

  结束语

  正如上述考虑事项所演
的设计问题。如果这些问题
的好处。但愿 Java 编程会
 示的那样,将成熟的运行时支持
处理得不当,那么可表达性和健
继续朝着维持这些属性的高度表
 添加到泛型 Java 要解决许多微妙
壮性的降低会轻易地抵消泛型类型
达性和健壮性的方向发展。
 

  下一次,我们将通过讨论或许是功能
数父类型的类)添加到语言中 — 来结束
式与先前讨论的这种功能强大的语言特性
 最强大的应用泛型类型的方法 — 将 mixin(具有参
对泛型类型的讨论。我们会将这种 mixin 的表现方
相关联,讨论通过泛型类型添加 mixin 的优缺点。
 
 
发表于:2004.12.24 16:51

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/12476590/viewspace-133986/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2007-12-21

  • 博文量
    257
  • 访问量
    500384