荔园在线

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

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


发信人: Peter (小飞侠), 信区: Program
标  题: 顶层类和内嵌类
发信站: BBS 荔园晨风站 (Wed Jan 27 17:45:18 1999), 转信


JDK1.1新语法:内嵌类(1)

Java在老版本中只提供了顶层类,它们必须是包的成员。然而现在,
新版的Java1.1允许程序员定义内嵌类(inner class),它们可以是其
它类的成员,或者局部地存在于一个程序块中,或者匿名地存在于一个
表达式中。

    顶层类和内嵌类的概念

    内嵌类的重要特性有:

    1.如果类名不合格,内嵌类的类名在其作用域以外无效。这有助
于在包内结构化类。
    2.内嵌类的代码可以使用包围作用域中的简单名,包括类、包围
类的实例成员,以及包围块的局部变量名。
    内嵌类是由基于类的编程和块结构相结合而产生的,这种程序结
构最先被Beta语言所采用。利用带有内嵌类的块结构,Java程序员可
以更容易地将对象联系起来,因为此时类可以被定义为更接近于它们
所要控制的对象,并且可以直接用它们所需要的名字。随着对类所处
位置的限制的消除,Java的作用域规则变得更加正规,就如同Pascal和
Scheme那些传统的块结构化语言一样。
    另外,程序员可以将类定义为顶层类的静态(static)成员。作为
静态类成员的类和作为包成员的类都被称为顶层类。它们与内嵌类的
区别在于:顶层类只能直接引用它自己的实例变量。以这种方式实现
类嵌套的能力可使顶层类为逻辑上相关的一组次级顶层类提供一种类
似包的组织结构,所有这些次级类共享对私有成员的全部读写权限。
    内嵌类和嵌套顶层类由编译器实现,并且不需要对Java虚拟机作
任何改变。它们不会破坏与现存Java程序在源代码级和二进制级上的
兼容性。
    所有这些新的嵌套类构造都被转换成不使用内嵌类的Java1.0代
码来加以解释。当Java 1.1编译器产生Java虚拟机字节代码时,这些
字节代码必须表达这种(假想的)由源代码到源代码的转换结果,从而
使不同的Java1.1编译器所产生的二进制代码可以兼容。这些字节代
码也必须加上一定的属性标志以便向其它的Java1.1编译器指明每个
嵌套类的存在。这将在下面进一步讨论。

    简单的接口类举例
    在这里我们设计一个接口类。这个类利用指定的一种代表另一个
对象而非其本身的界面类型来接收方法调用。为了能接收来自AWT和J
avaBean构件的事件,接口类通常都是需要的。在Java1.1中,接口类最
容易被定义为内嵌类,置入需要接口的类中。
    以下是一个不完整的类FixedStack,它实现一个堆栈,并且可以自
顶向下对栈中的元素计数:
    public  class  FixedStack  {
       Object  array[];
       int  top = 0;
       FixedStack ( int  fixedSizeLimit ){
    array = new  Object [fixedSizeLimit];
       }
       public  void  push ( Object  item ){
    array [top++] = item;
       }
       public  boolean  isEmpty( ) {
    return  top = 0;
       }
       // other  stack  methods  go  here...
       class  Enumerator  implements  java.util.Enumeration
        int  count = top;
        public  boolean  hasMoreElements( )  {
     return  count > 0;
        }
        public  Object  nextElement( )  {
        if (count == 0 )
     throw  new  NoSuchElementException( "Fix" );
       return  array[- - count];
        }
       }
       public  java.util.Enumeration  elements( )  {
        return  new  Enumerator( );
       }
      }
    接口java.util.Enumeration用来传送一系列值给客户。由于Fix
edStack不能(而且不应该!)直接实现Enumeration接口,因此另外需要
一个接口类来以Enumeration的形式描述这一系列元素。当然,这个接
口类需要具备读写堆栈数组元素的某种权限。如果程序员把这个接口
类的定义放入类FixedStack中,则该接口类的代码可以直接引用堆栈
对象的实例变量。
    在Java语言中,类的非静态成员可以互相引用,而且它们都采用与
当前实例this相关的含义。因此,类FixedStack的实例变量array可以
为实例方法push所引用,也可以在内嵌类Fixe dStack.Enumerator的
整个类体中引用。正如实例方法的主体都"知道"其当前实例this一样
,任何一个像Enumerator这样的内嵌类中的代码也都"知道"其包围实
例,即类似array这样的变量所取自的包围类的实例。
    造成例子FixedStack不完整的原因之一是在FixedStack的操作和
它的内嵌类Enumerato r之间存在一个竞争条件。当一系列的入栈和
出栈操作在对nextElement的两次调用之间发生时,返回值可能和上次
的计数值无关,甚至可能返回处于当前栈尾之外的"垃圾值"。程序员
有责任防止这种竞争条件的情况出现,或者建立针对这个类的使用限
制文档。以下是防止这种竞争的一种方法:
    public  class  FixedStack  {
     ...
     synchronized  public  void  push ( Object  item ) {
       array[top++] = item;
     }
     class  Enumerator  implements  java.util.Enumeration
      ...
      public  Object  nextElement( )  {
        synchronized ( FixedStack.this ) {
    if ( count > top )   count = top;
    if ( count == 0 )
     throw  new  NoSuchElementException ("Fix");
    return  array[- - count];
        }
      }
     }
    其中表达式FixedStack.this指的是包围实例。
    局部类举例
    当一个类被定义成一个块中的局部类时,它可以读写同一块中的
一般表达式所能引用的一切名称。以下是一个例子:
    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( );
    }
    在以上这段代码中,Java的作用域规则和变量语义学规则得到精
确体现。甚至在方法my Enumerate返回之后,array仍能被内嵌对象所
引用,而不像在C语言中那样"离开"了。相反,它的值可以在包括类E的
两个方法在内的任何需要的地方继续引用。
    注意最终模式(final)的声明。像array这样的局部最终模式变量
是Java1.1的一个新特点。实际上,如果一个类中的局部变量或局部参
数要被另一个类(或内嵌类)所引用,那么它必须被声明为最终模式。
由于潜在的同步问题,没有办法实现两个对象共享对同一可变局部变
量的读写。状态变量count不能编码为局部变量,除非将它变为一元数
组,如下所示:
    Enumeration  myEnumerate ( final  Object  array[])  {
     final  int  count[]= 0;  // final  reference
     class  E  implements  Enumeration  {
      public  boolean  hasMoreElements( )
        {  return  count[0] < array.length;  }
     }
      }
    继承和词汇作用域相结合有时可能会令人迷惑。例如,如果类E从
Enumeration继承了一个名叫array的域,这个域将把包围作用域中的
同名参数掩藏掉。为了防止出现二义性,Java 1.1允许被继承的域掩
藏掉那些在包围块或包围类作用域内定义的同名参数,但是禁止它们
在不具备显式的资格时被引用。

    匿名类
    在上面的例子中,局部类名E几乎没有或者可以说完全没有增加代
码的清晰度。问题不在于它太短:一个再长的名字也不能使维护者扫
一眼类体就能明白类中的内容。为了构造尽可能简洁的非常小的接口
类,Java1.1允许使用局部对象的缩写表示。一种简单的表达式文法把
匿名类的定义和实例的分配相结合,如下所示:
    Enumeration  myEnumerate ( final  Object  array[])  {
     return  new  Enumeration( )  {
       int  count = 0;
       public  boolean  hasMoreElements( )
    {  return  count < array.length;  }
       public  Object  nextElement( )
    {  return  array[count++];  }
     };
      }
    一般地说,一个new表达式(实例创建表达式)可以以类体作为结束
。这样做可以使该类( 或接口)在new标志之后命名,并且根据给定的
类体来产生它的子类(或实现它)。结果产生的匿名内嵌类同程序员在
当前语句块中用一个名字局部定义它具有同样的意义。
    这样的匿名构造必须保持简单性以避免代码嵌套过深。如果正确
使用,它们比命以一定名字的局部类或顶层接口类都更易理解和维护

    如果一个匿名类包括一两行以上的可执行代码,那么它的意思很
可能不是显而易见的,因此应该给该类或通过一个局部变量给该实例
一个描述性的局部名称。
    匿名类可以有初始化程序,但不能有构造程序。相关的new表达式
的变元表(常为空)隐式地传递给超类的构造程序。
    正如前面所提示的一样,如果匿名类是由接口I派生得来的,那么
它实际的上级类是Obje ct,而且它实现I而非扩展之(显式的implemen
ts子句是非法的)。这是接口名称能合法地放在关键字new后面的唯一
方法。在这种情况下,变元表必须总是空的,以便与实际的上级类Obje
ct的构造程序相匹配。

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


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

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