性格决定命运 气度左右格局 拼搏方能取胜 谦虚才会进步

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

上一篇 / 下一篇  2008-01-10 18:02:54 / 个人分类:做着

轻松掌握 Java 泛型 (第 3 部分)ITPUB个人空间X{0_7f _
来源:** 收集整理

 
/~u^'O Z7J0 ITPUB个人空间VY$`/fDAR5W
 ITPUB个人空间 Va6eVn M8FQu
 
o7bI \%t7}.j}B0 
tB!El?p[0  有效的构造函数调用

  首先,为了对类型参数ITPUB个人空间4j!t!C2id!t
造函数对于 T 的每个实例
s LW-WQkP.x0我们不知道 T 的某一实例ITPUB个人空间 `W5W;k0P Xm
ITPUB个人空间 ~(Gw2w(R2| C
 构造合法的 new 表达式(如 neITPUB个人空间*|7f[gYq$e'q$A0kO$t2f
化都有效。但由于我们只知道 T
bl zT*B`"m-~0化将有什么构造函数。要解决这

 w T()),必须确保我们调用的构
p0B f ^/DI0是其已声明界限的子类型,所以
4DbO,ng:Bge0一问题,可以用下述三种方法之一

 

  要求类型参数的所有实例化都包括不带参数的(zeroary)构造函数。          ITPUB个人空间/~1]A7OWx
  只要泛型类的运行时实例化没有包括所需的构造函数,就抛出异常。                  ITPUB个人空间Xo3hm&p i
  修改语言的语法以包括更详尽的类型参数界限。                                    

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

  只要求类型参数的所有ITPUB个人空间ht-QnD U o
。使用这种方法也有先例。ITPUB个人空间9m!W!kom
 实例化都包括不带参数的构造函

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

 

  处理类似问题的现有 JITPUB个人空间 S-h&I&m^/y U6B&C
函数来解决问题的。然而,ITPUB个人空间-W d$s.s[OW
造函数。
~)?6MCQ^T8}0 ava 技术(象 JavaBean 技术)
I_]]2|K/wr~0该方法的一个主要缺点是:对于

 就是通过要求一个不带参数的构造ITPUB个人空间Fc0H;^2}is1|e
许多类,没有合理的不带参数的构

 

  例如,表示非空容器的任何类在构造ITPUB个人空间$e5C7Y6g7T
构造函数将迫使我们先创建实例,然后再
5^1S:^E![:VC-h0实践会导致问题的产生(您可能想要阅读
!E6dD[3}x&S0Initializer bug pattern”,以获取详
E(y\KMDTS0 函数中必然使用表示其元素的参数。包括不带参数的
$l])i4\s0s~0进行本来可以在构造函数调用中完成的初始化。但该ITPUB个人空间HjJ-k{ |1fkG
2002 年 4 月发表的本专栏文章“The Run-onITPUB个人空间h%x2x4w1qf4s
细信息;请参阅参考资料。)
u_uQZ LZ|z0 

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

  处理该问题的另一种方法是:只要泛
/e%~.MLqS0异常。请注意:必须在运行时抛出异常。ITPUB个人空间!`5lK^m
态地确定所有将在运行时发生的泛型类的ITPUB个人空间$F;_B,hR
 型类的运行时实例化没有包括所需构造函数,就抛出ITPUB个人空间@;?,K)p"[7]I_2F
因为 Java 语言的递增式编译模型,所以我们无法静
R J'] i:YPX0实例化。例如,假设我们有如下一组泛型类:
g:`&v~T0@I J0 

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

  class C< T> {

   T makeT() {                                                      
C"i J,IOQ `0    return new T();                                            
cG-o [:_8T+m_0   }                                                                          
/^$Ei@x#WYT0  }                                                                            

  class D< S> {

   C< S> makeC() {

    return new C< S>();

   }                                                                          
#y Tw {ou8dUR5m0  }                                                                            

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

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

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

ITPUB个人空间#}^9EKag.u!@,~
  还有,对所报告错误的
.~hf _.lB Y0现在,让我们假设程序员无ITPUB个人空间EUV|'n;m&J.z
绪,除非他设法联系类 C
xf9Z/h,K+g VSX0 堆栈跟踪甚至可能不包括任何对ITPUB个人空间~ S3e_o%t\r
权访问类 C 的源代码。他对问ITPUB个人空间 or(MA+ge/D-D^5t5`s4k
的维护者并获得线索。ITPUB个人空间8L[6`l&~f4f@
 这个糟糕的 D 实例的方法调用!
_;UAC3K"pi0题是什么或如何修正代码将毫无头

 

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

  另一种可能性是修改语言语法以包括
wrhf-sh l3z(X5C0Q0的构造函数,它们必须出现在参数的每一
3[b"sm7y`r%Y/Z0的构造函数是那些在界限中声明的构造函
|xYuA%? }0 更详尽的类型参数界限。这些界限可以指定一组可用ITPUB个人空间H O Op(^$k,ik
个实例化中。因而,在泛型类定义内部,唯一可调用
SP.AOa6]w'o7bqM0数。
"KC.\$e0r4d0 

  同样,实例化泛型类的
1`0f/Q ?"y0。参数声明将充当类与其客

 客户机类必须使用满足对构造函
V(x&s:O5Bv0户机之间的契约,这样我们可以

 数存在所声明的约束的类来这样做
]&F%b;nAO0静态地检查这两者是否遵守契约。

 

  与另外两种方法相比,ITPUB个人空间 P3e?#g`-k
第一种方法中相同的静态检
L S9@k @0 该方法有许多优点,它允许我们
~%[f_0Vr c _0查程度。但它也有需要克服的问ITPUB个人空间+_0W[_0L6r
 保持第二种方法的可表达性以及与ITPUB个人空间G'J;cEG7R bI
题。ITPUB个人空间 w/|X{4['\
 

  首先,类型参数声明很ITPUB个人空间V#}VE6h]:\
充的参数声明还过得去。另
4TY0m1w5Dh%g@0必须确保这些扩充的声明将ITPUB个人空间uMw9e0ST/^.mV
 容易变得冗长。我们或许需要某
iO8eT8W/_!Y$B0外,如果在 Tiger 以后的版本ITPUB个人空间b;m/s^aqIw W.u3lD
与现有的已编译泛型类兼容。
~!i*G8Ud0rA Ij0 种形式的语法上的甜头,使这些扩
*B!p/C\$Q!uY0中添加扩充的参数声明,那么我们

 

  如果将对泛型类型的与类型相关的操ITPUB个人空间%m7i} ~^
还不清楚。但是,从哪种方法将使 Java
;\co m s:_X{J0能容易地修正)的观点看,第三个选项无ITPUB个人空间$a0}*Tma$^z t K
 作的支持添加到 Java 编程中,那么它采用何种形式ITPUB个人空间+` _1UW-z*G \7s%hN-_
代码尽可能地保持健壮(以及使在它遭到破坏时尽可
/l,Y1o.NY9^Z0疑是最适合的。ITPUB个人空间^"ula#xjB
 

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

  多态递归

  更严重的问题是类定义中可能存在多
9~w/o%_h(C9X?N\0,发生多态递归。例如,考虑下面的错误ITPUB个人空间n?7@qv0e)`$q
 态递归。当泛型类在其自己的主体中实例化其本身时ITPUB个人空间D(p&Uh-} ^'c8qXK
示例:ITPUB个人空间|$KcV I!J+Sh
 

  清单 2. 自引用的泛型类                                                    

  class C< T> {

   public Object nest(int n) {                      
G9q `9^ }0    if (n == 0) return this;                          
T s \Lafx0    else return new C< C< T>>().nest(n - 1);

   }                                                                          ITPUB个人空间8@!k:d&p]!orY$b
  }                                                                            

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


'j?6h/qo0  为什么这成为问题呢?
W]:C;Ko0型相关的操作,那么,在程
@ {{zo#gTY0入器为它所装入的每个类查
.o#j#a6`7A0 因为如果我们通过为每个实例化ITPUB个人空间HN#P,k)n
序运行以前,我们无法知道我们
1q"}OF wHZ?0找现有类文件,那么它会如何工
5e2Ww HQQ0 构造独立类来支持泛型类型的与类
mi"En]at Y0需要构造哪些类。但是,如果类装
Bu:kb:K|0作呢?
x S^%MV!Q{0 

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

  对程序可以产生的泛型类的实例化数目设置上限。                                  ITPUB个人空间XK8u3tM.t%w;L
  静态禁止多态递归。                                                            
&_ vv[nV;C.|k0  在程序运行时随需构造新的实例化类。                                            

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

  我们对程序可以产生的ITPUB个人空间&U2n(C0gV8E \3C[D
组合法的实例化确定有限界
PjqZ"LO+ZC i6`0 泛型类的实例化数目设置上限。ITPUB个人空间 t^){ cNiHjF
限,并且仅为该界限中的所有实
z i"O9Etn9l:w!Z0 然后,在编译期间,我们可以对一
){8G;q#oyNz0例化生成类文件。ITPUB个人空间 Bcx^6q L+fg3m@
 

  该方法类似于在 C++ 标准模板库中
?J7\g3NwpOK3ut0)。该方法的问题是,和为错误的构造函ITPUB个人空间UJ Ax+P D
一次运行将崩溃。例如,假设实例化数的ITPUB个人空间^0tI%H"R`:d
的 nest() 方法。那么,只要用户输入小
[9W;] {@t0计划不周的设计就会失败。现在,设想一ITPUB个人空间ep)LH e.\7E
码并试图弄清楚幻数 42 有什么特殊之处
'j-N&d3U?;L|0 完成的事情(这使我们有理由担心它不是一个好方法
&y3eLL`Gi(v&p0数调用报告错误一样,程序员将无法预知其程序的某
&Px-|v#qgK3E3pq0界限为 42,并且使用用户提供的参数调用先前提到ITPUB个人空间t(G;t,J&Y%Y_;IX%h|
于 42 的数,一切都正常。当用户输入 43 时,这一
!V;Tw h~"a;} Y0下可怜的代码维护者,他所面对的任务是重新组合代ITPUB个人空间$A`#gN3U9U

+@M6Zm{C0 

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

  为什么我们不向编译器
4wg*`6`ms ^S w ?0单就好了。)当然,包括我ITPUB个人空间s*O{2ZLp z
的使用。ITPUB个人空间#R;W-LH;B P l'~
 发出类似“静态禁止多态递归”ITPUB个人空间T"sNm0p4r
在内的许多程序员都会反对这种

 这样的命令呢?(唉!要是那么简
,l"P1}HN` c;c d0策略,它抑制了许多重要设计模式

 

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


8l"l4X B#Wd7^0  下面的示例涉及两个类之间的多态递归:                                          

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

  class C< T> {

   public Object potentialNest(int n) {    ITPUB个人空间J$f3?E x`g I
    if (n == 0) return this;                          ITPUB个人空间$x#A*V ] q @E
    else return new D< T>().nest(n - 1);

   }                                                                          ITPUB个人空间-n(MCveaR6H"x
  }                                                                            

  class D< S> {

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

   }                                                                          
FKmx}P0  }                                                                            

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

ITPUB个人空间TY?D;hTQN R3Z
  或许,我们可以将新属ITPUB个人空间g2CDM1Y K*b)c[#[
编译其它类时分析这些实例ITPUB个人空间"m]4O4yrp5yi
观的错误消息。
q ~G$] F,J^S5}0 性添加到类文件中,以表明类中
,jY8o ?7wi8LG@&}0化,以进行递归。但是,我们还

 所有不同泛型类型实例化,然后在ITPUB个人空间h \&Xxb%h
是必须向程序员提供奇怪的和不直

 

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

ITPUB个人空间'F8A2Qj~ n(_ [l.e"Q#x
  第 3 种:实时构造新的实例化类 

  另一种方法是在程序运行时按需构造ITPUB个人空间0P6] ?&C'j;Q9H
完全不兼容。但实际上,实现该策略所需
o"vG/BWe!D0(template)”类文件构造新的实例化类ITPUB个人空间`CO&z([#Z-W\
 新的实例化类。起先,这种方法似乎与 Java 运行时
(r ]/sQA0的全部就是使用一个修改的类装入器,它根据“模板
$m(x0C9`np I0
IuL6W-E*q0 

  JVM 规范已经允许程序
!u0`#sN7S@k\;I$_vi@ ]0Ant、JUnit 和 DrJava)
R)?@9cr,Ut-L0一起分布,以在较旧的 JVM
oI*W*D YV0 员使用修改的类装入器;事实上
j`[NY0都使用它们。该方法的缺点是:
9asB qt0上运行。因为类装入器往往比
0p${#PC6[1XO0 ,许多流行的 Java 应用程序(如
ANmZq7J C$Y0修改的类装入器必须与其应用程序
9h_Tv2B-c]9l Z0较小,所以这个开销不会大。ITPUB个人空间r"]|k8ZRM
 

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

  NextGen 示例:修改的类装入器                                          ITPUB个人空间 ]I d/g A2W$V a}J
  前一种方法 — 用按需构造泛型类型
EC6dfO*L0Java 语言的 NextGen 扩展所采用。修改ITPUB个人空间E fz,@@-f
模板文件,不同的是这个模板文件在常量ITPUB个人空间;W v@GoY)u.s
些“洞”。非泛型类不受影响。ITPUB个人空间KwA}zD [1uC X&j$H
 实例化的修改的类装入器解决多态递归问题 — 被ITPUB个人空间*j#C(gL3~,vWNZ
的类装入器使用看上去几乎与普通类文件完全一样的
?0~&K*c:g s0池中有一些“洞”,在装入时为每个实例化类填充这

 

  在 Rice 大学 JavaPLT
:m$vP$v6Y0是 GJ 泛型 Java 编译器的ITPUB个人空间4sy:UE'l,R
转换、instanceof 测试
ZFsv.CXT0来支持多态递归。可以免费ITPUB个人空间 r i]ji.B*r;t[QN%F
 编程语言实验室,我们最近发
].Z!li!Xb5I c0一种扩展,这种扩展支持类型参ITPUB个人空间 sd2b]9A)IJX
new 表达式)。在该原型实现中
ifj5c7o0下载该原型(请参阅参考资料)ITPUB个人空间W)iR*W5}z j
 布了 NextGen 编译器的原型,它
MJK1Do f0数的与类型相关的操作(数据类型ITPUB个人空间0DSxf]:f.rH
,我们使用了一个修改的类装入器ITPUB个人空间:\1eDd"`

&?Y*n_H?_0 

  结束语

  正如上述考虑事项所演ITPUB个人空间WvTP)c GfnM!Y
的设计问题。如果这些问题ITPUB个人空间F9A&Z v}9pf#_{:M8r
的好处。但愿 Java 编程会ITPUB个人空间2OZxYq
 示的那样,将成熟的运行时支持
)Vj+mQ;w |;d0处理得不当,那么可表达性和健
T syN:k+v.B:[5c0继续朝着维持这些属性的高度表
'}&k7V`z E"K0 添加到泛型 Java 要解决许多微妙
9j&t-~4J b/C)VzS0壮性的降低会轻易地抵消泛型类型ITPUB个人空间r C{-x-b+AYf
达性和健壮性的方向发展。
@(\/D6]X^(X0 

  下一次,我们将通过讨论或许是功能
P@%Ua?/eFn0数父类型的类)添加到语言中 — 来结束ITPUB个人空间!F0F3{ KPH^P4l
式与先前讨论的这种功能强大的语言特性ITPUB个人空间5tZ V4O K}
 最强大的应用泛型类型的方法 — 将 mixin(具有参
j [ _ vc.\fk0S0对泛型类型的讨论。我们会将这种 mixin 的表现方ITPUB个人空间U*j4Txww
相关联,讨论通过泛型类型添加 mixin 的优缺点。
y~aZ;G0vy%F0 ITPUB个人空间4D3p$ck(rw(m0Ur2n
 
:tv$z&}1P4fT s0发表于:2004.12.24 16:51


TAG:

 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

Open Toolbar