荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Peter (小飞侠), 信区: Program
标  题: 内嵌类如何工作?
发信站: BBS 荔园晨风站 (Wed Jan 27 17:46:34 1999), 转信


JDK1.1新语法:内嵌类(2)
内嵌类工作方式
    一般情况下,内嵌类代码与某些封装类实例相关,因此,内嵌类实
例能够定义封装实例。
    Java 1.1编译器通过加入附加的私有(private)实例变量来做到
这一点,该变量能连接内嵌类至封装类,它初始化为一个附加参数,传
递给内嵌类构造程序。参数依次由生成内嵌类实例的表达式定义;或
被默认为是完成此生成工作的对象。
    Java 1.1特别指出,为了生成Java虚拟机字节代码,当转化为Java
1.0代码时,除了每一个跟在类名后的". "由"$"替代之外,作为类成
员的类型名由内嵌类的完全限制名组成。另外,每一个内嵌类构造程
序收到在prepended参数中的封装实例。从FixedStack例子的源代码
中可以看出是如何转换的。
    public class FixedStack  {
       ...(the methods omitted here are unchanged)
       public java.utill.Enumeration elements()  {
         return new FixedStack$Enumerator(this);
       }
    }
    class FixedStack$Enumerator imp_ements java.util.Enumera
tion{
    private FixedStack this$0;  //saved copy  of Fixed
    FixedStack$Enumerator(FixeedStack this$0)  {
      this.this$0 = this$0;
      this.count = this$0.top;
    }
    int count;
    public boolean hasMoreElements()  {
      return count > 0;
    }
    public Object nextElement()  {
      if (count == 0)
      throw new NOSuchElementException("FixedStack");
      return this$0,array[- - count];
    }
    }
    任何用过Java或C++接口类的编程人员都能写出类似的代码,只是
连接变量必须由人为定义,同时在顶层接口类中初始化。Java 1.1则
前进了一步,能为内嵌类自动生成。
    当枚举器(Enumerator)需要引用封装类中的顶(Top)或数组(Arra
y)域时,它间接通过一个私有(private)连接调用this$0。这个类名的
拼写是内嵌类向Java 1.0语言转化的部分命令,这样调试程序或类似
的工具能很容易地识别这种连接(大部分程序员还不知道这样的名字)

    需要注意的是,Java 1.1的新增部分中有一限制:this$0的初始化
会延迟到上级类构造程序运行以后。这意味着如果方法正好是由上级
类构造程序执行,则下级类方法制定的上级引用将会失败。

    局部变量引用
    对于块来说是局部的类定义可以访问局部变量,但会使编译器的
工作复杂化。这里有一个局部类的例子:
    Enumeration myEnumerate (final Object array[] ) {
    class E implements Enumeration  {
      int count = 0;
      public boolean hasMoreElements()
      {
        return count < array.length;
      }
      public Object nextElement()
      {
        return array[count++];
      }
    }
    return new E();
    }
    为了使局部变量内嵌类方法可见,编译器必须复制该变量的值并
将其放到内嵌类能访问的地方。同一变量的引用可以在不同地方使用
不同的代码顺序。只要每一处都产生同样的值,那么在该区域内各个
部分中固定的类名会引用同一变量。
    按惯例,像array那样的局部变量可以复制到内嵌类的val$array
中的私有域里(因为a rray是final型,所以这些复制不能包含互相矛
盾的值)。每一复制值送到内嵌类构造程序内,作为分立的同名参数。
    下面是生成的转换代码形式:
    Enumeration myEnumerate (final Object array[] ){
      return new MyOuterClass$19(array);
    }
    …
    class MyOuterClass$19 implements Enumeration  {
      private Object val$array[];
      int count;
      MyOuterClass$19 (Object val$array[])
      {
        this.val$array = val$array.length; count = 0;
      }
      public boolean hasMoreElements()
      {
        return count < val$array.length;
      }
      public Object nextElement()
      {
        return val$array[count++];
      }
    }
    如果能够确定某一变量仅在内嵌类构造程序内使用,编译器就可
以不给该变量分配内嵌类域。
    注意:由块定义的类,如E,它不是自身封装类的成员,因此它不能
在块的外面命名。使用局部变量时也有这种区域限制,它们也不能在
块的外面命名。实际上任何包含在块中的类(无论直接地还是在插入
局部类之中)都不能在块的外面命名。所有这样的类称为不可访问的
。为了连接,编译器必须给每一个不可访问的类生成一个唯一的外部
可见名。这些名称的形式是从左到右,加上由$字符分隔,再附加数字
或名称,即成为类名。
    同样,变量名也由编译器合成,合成开始于this$,而且val$必须跟
着其中所描述的使用模式。
    这些名称和惯例必须能被1.1兼容的工具所识别,并且力争达到最
佳的编译效果。
    这些奇怪的"this$"和"val$"域以及附加的构造程序参数都是由
编译器加入到生成的字节代码中,不能被Java源代码直接引用。一样,
字节代码级类名,如MyOuterClass$19,也不能被源代码所使用(除非在
1.1以前的编译器下,它对内嵌类一无所知)。
    为什么Java需要内嵌类?
    从Java的真正起源起,它的设计者们就认识到需要一个像"函数指
针"那样的结构,它有各种形式,相当于一个单独的块代码上的句柄,无
需引用对象或包含代码的类,就能够使用。在一些语言(如C或Lisp)中
,函数有自主的地位,独立于对象,其中函数指针起到了上述作用。比
如,指针通常用来连接一个模块中的"回调"或"事件"和另一个模块中
的一段代码。在面向对象风格中,Smalltalk具有"块",它们是大量的
代码,其行为像对象。当利用C或Lisp函数指针时,Smalltalk块能组成
复杂的控制流模型,诸如集合的遍历。
    Java中复杂的控制流模型,包括事件管理和遍历,也通过类和接口
来表达。Java使用接口连接某种方法,在该方法中其它语言也许在使
用分离的"函数类型"。Java程序员通过把期望的代码封装到一个用来
实现所要求的接口类中,来生成一个相当于回调或Smalltalk块的等价
物。利用内嵌类,接口的描述将像Smalltalk块或其它语言中的内部函
数一样简单。然而,由于类比函数更加丰富(因为它们有多个入口点),
所以Java接口对象比函数指针更加强大,更加结构化。
    C、Lisp和Smalltalk程序员使用"方法指针 "的变形体来封装大
量代码,而Java程序员利用对象。当其它语言使用特定的函数类型和
表示把行为封装成函数时,Java仅使用类和接口类型。在Java中,"类
是行为的最小组成"。对Java虚拟机来说,这种方法的一大益处是简单
且稳定,它无需特别支持内嵌类或函数指针。
    没有内嵌类,Java程序员也能够利用顶层定义的接口类生成回调
和遍历,但是表达很笨拙,很不实用。利用内嵌类,Java程序员能编写
简洁的接口类,它们在需要的地方精确编码,且直接作用于内部变量以
及类和块的方法之上。
    这样,内嵌类使得接口类形成实用的编码风格。今后,随着不可访
问类特别是外部的不可访问类的优化不断增加,内嵌类将比等价的顶
层类具有更大的效力。
    为什么要匿名类?
    匿名类是一种简短的表达,只要简单地在"new"表达式中封装所需
的代码,就能够在任何表达式内部生成简单的局部对象"inline"。
    正如前面所提到的,并不是每一个内嵌类都必须匿名,但是简单的
"one-shot"局部对象是一种公用例子,它们具有很多语法优点。
    匿名类可用来编写小的封装的"回调 ",诸如枚举、遍历、访问等
等。它们也有助于在已有名的对象中加入行为,如AWT组件(无名事件
的句柄被加在其中)和线程。在这两种情况下, 插入类名可以从代码
清单中去掉。
    一些其它语言,如Smalltalk和Beta,它们从Java中获得灵感,为匿
名对象或函数提供了类似的简写。

    关于动态打印和计算选择器及"perform"
    为了维持系统的健壮性和安全性结构,Java静态打印。在其它语
言中,回调通常采用不打印或动态打印的形式。C回调一般与不打印"
客户数据 "地址一起工作,而Smalltalk类通常利用运行时计算的象征
方法引用来使它们相互插入,象征方法引用传递到说明"perform"方法

    Java中与C语言Void指针最接近的等价物是打印对象引用。和C语
言中一样,在Java中它能规划那些"不打印"(untyped)引用。当事件描
述器里一般的"参数"域是java.util.vector 的元素类型时,它可能是
一个不同的对象。尽管执行耗费动态打印检测,但是采用不打印引用
通常仍是很有效的技巧。然而由于缺乏静态声明,可能会使程序难于
理解和维护。
    "方法指针"结构的应用,诸如应用构造器或JavaBeans组件框架,
要求能够调用专有对象上的计算名方法。Java Core ReflectionAPI
中的java.lang.reflect提供了这种功能,它是一个新的Java 1.1 API


--
※ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: 192.168.1.3]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店