友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第37部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!



      s。add(Integer。toString(i));  

    Selector sl = s。getSelector();      

    while(!sl。end()) {  

      System。out。println((String)sl。current());  

      sl。next();  

    }  

  }  

} ///:~  

  

②:这与C++ “嵌套类”的设计颇有不同,后者只是一种单纯的名字隐藏机制。在C++中,没有指向一个封装 

对象的链接,也不存在默认的访问权限。  

  

其中,Sequence 只是一个大小固定的对象数组,有一个类将其封装在内部。我们调用add(),以便将一个新 

对象添加到 Sequence 末尾(如果还有地方的话)。为了取得Sequence 中的每一个对象,要使用一个名为 

Selector 的接口,它使我们能够知道自己是否位于最末尾(end()),能观看当前对象(current()  

Object),以及能够移至 Sequence 内的下一个对象(next() Object )。由于Selector 是一个接口,所以其 

他许多类都能用它们自己的方式实现接口,而且许多方法都能将接口作为一个自变量使用,从而创建一般的 

代码。  

在这里,SSelector 是一个私有类,它提供了 Selector 功能。在main()中,大家可看到Sequence 的创建过 

程,在它后面是一系列字串对象的添加。随后,通过对getSelector()的一个调用生成一个Selector 。并用 

它在Sequence 中移动,同时选择每一个项目。  

从表面看,SSelector 似乎只是另一个内部类。但不要被表面现象迷惑。请注意观察 end(),current()以及 

next(),它们每个方法都引用了o。o 是个不属于 SSelector 一部分的句柄,而是位于封装类里的一个 

private 字段。然而,内部类可以从封装类访问方法与字段,就象已经拥有了它们一样。这一特征对我们来 

说是非常方便的,就象在上面的例子中看到的那样。  

因此,我们现在知道一个内部类可以访问封装类的成员。这是如何实现的呢?内部类必须拥有对封装类的特 

定对象的一个引用,而封装类的作用就是创建这个内部类。随后,当我们引用封装类的一个成员时,就利用 

那个(隐藏)的引用来选择那个成员。幸运的是,编译器会帮助我们照管所有这些细节。但我们现在也可以 

理解内部类的一个对象只能与封装类的一个对象联合创建。在这个创建过程中,要求对封装类对象的句柄进 

行初始化。若不能访问那个句柄,编译器就会报错。进行所有这些操作的时候,大多数时候都不要求程序员 

的任何介入。  



7。6。4 static 内部类  



为正确理解 static在应用于内部类时的含义,必须记住内部类的对象默认持有创建它的那个封装类的一个对 

象的句柄。然而,假如我们说一个内部类是static 的,这种说法却是不成立的。static 内部类意味着:  

(1) 为创建一个 static 内部类的对象,我们不需要一个外部类对象。  

(2) 不能从 static 内部类的一个对象中访问一个外部类对象。  

但在存在一些限制:由于 static 成员只能位于一个类的外部级别,所以内部类不可拥有static 数据或 

static 内部类。  

倘若为了创建内部类的对象而不需要创建外部类的一个对象,那么可将所有东西都设为static。为了能正常 

工作,同时也必须将内部类设为static。如下所示:  

  

//: Parcel10。java  

// Static inner classes  



                                                                                 187 


…………………………………………………………Page 189……………………………………………………………

package c07。parcel10;  

  

abstract class Contents {  

  abstract public int value();  

}  

  

interface Destination {  

  String readLabel();  

}  

  

public class Parcel10 {  

  private static class PContents   

  extends Contents {  

    private int i = 11;  

    public int value() { return i; }  

  }  

  protected static class PDestination  

      implements Destination {  

    private String label;  

    private PDestination(String whereTo) {  

      label = whereTo;  

    }  

    public String readLabel() { return label; }  

  }  

  public static Destination dest(String s) {  

    return new PDestination(s);  

  }  

  public static Contents cont() {  

    return new PContents();  

  }  

  public static void main(String'' args) {  

    Contents c = cont();  

    Destination d = dest(〃Tanzania〃);  

  }  

} ///:~  

  

在main()中,我们不需要Parcel10 的对象;相反,我们用常规的语法来选择一个 static 成员,以便调用将 

句柄返回Contents 和 Destination 的方法。  

通常,我们不在一个接口里设置任何代码,但 static 内部类可以成为接口的一部分。由于类是“静态”的, 

所以它不会违反接口的规则——static 内部类只位于接口的命名空间内部:  

  

//: IInterface。java  

// Static inner classes inside interfaces  

  

interface IInterface {  

  static class Inner {  

    int i; j; k;  

    public Inner() {}  

    void f() {}  

  }  

} ///:~  

  



                                                                                             188 


…………………………………………………………Page 190……………………………………………………………

在本书早些时候,我建议大家在每个类里都设置一个main(),将其作为那个类的测试床使用。这样做的一个 

缺点就是额外代码的数量太多。若不愿如此,可考虑用一个 static 内部类容纳自己的测试代码。如下所示:  

  

//: TestBed。java  

// Putting test code in a static inner class  

  

class TestBed {  

  TestBed() {}  

  void f() { System。out。println(〃f()〃); }  

  public static class Tester {  

    public static void main(String'' args) {  

      TestBed t = new TestBed();  

      t。f();  

    }  

  }  

} ///:~  

  

这样便生成一个独立的、名为 TestBedTester 的类(为运行程序,请使用“java TestBedTester ”命 

令)。可将这个类用于测试,但不需在自己的最终发行版本中包含它。  



7。6。5  引用外部类对象  



若想生成外部类对象的句柄,就要用一个点号以及一个this 来命名外部类。举个例子来说,在 

Sequence。SSelector 类中,它的所有方法都能产生外部类Sequence 的存储句柄,方法是采用Sequence。this 

的形式。结果获得的句柄会自动具备正确的类型(这会在编译期间检查并核实,所以不会出现运行期的开 

销)。  

有些时候,我们想告诉其他某些对象创建它某个内部类的一个对象。为达到这个目的,必须在 new 表达式中 

提供指向其他外部类对象的一个句柄,就象下面这样:  

  

//: Parcel11。java  

// Creating inner classes  

package c07。parcel11;  

  

public class Parcel11 {  

  class Contents {  

    private int i = 11;  

    public int value() { return i; }  

  }  

  class Destination {  

    private String label;  

    Destination(String whereTo) {  

      label = whereTo;  

    }  

    String readLabel() { return label; }  

  }  

  public static void main(String'' args) {  

    Parcel11 p = new Parcel11();  

    // Must use instance of outer class  

    // to create an in stances of the inner class:  

    Parcel11。Contents c = p。new Contents();  

    Parcel11。Destination d =  

      p。new Destination(〃Tanzania〃);  

  }  



                                                                                          189 


…………………………………………………………Page 191……………………………………………………………

} ///:~  

  

为直接创建内部类的一个对象,不能象大家或许猜想的那样——采用相同的形式,并引用外部类名 

Parcel11 。此时,必须利用外部类的一个对象生成内部类的一个对象:  

Parcel11。Contents c = p。new Contents();  

因此,除非已拥有外部类的一个对象,否则不可能创建内部类的一个对象。这是由于内部类的对象已同创建 

它的外部类的对象“默默”地连接到一起。然而,如果生成一个static 内部类,就不需要指向外部类对象的 

一个句柄。  



7。6。6  从内部类继承  



由于内部类构建器必须同封装类对象的一个句柄联系到一起,所以从一个内部类继承的时候,情况会稍微变 

得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象 

可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种关联:  

  

//: InheritInner。java  

// Inheriting an inner class  

  

class WithInner {  

  class Inner {}  

}  

  

public class InheritInner   

    extends WithInner。Inner  {  

  //! InheritInner() {} // Won't pile  

  InheritInner(WithInner wi) {  

    wi。super();  

  }  

  public static void main(String'' args) {  

    WithInner wi = new WithInner();  

    InheritInner ii = new InheritInner(wi);  

  }  

} ///:~  

  

从中可以看到,InheritInner 只对内部类进行了扩展,没有扩展外部类。但在需要创建一个构建器的时候, 

默认对象已经没有意义,我们不能只是传递封装对象的一个句柄。此外,必须在构建器中采用下述语法:  

enclosingClassHandle。super();  

它提供了必要的句柄,以便程序正确编译。  



7。6。7  内部类可以覆盖吗?  



若创建一个内部类,然后从封装类继承,并重新定义内部类,那么会出现什么情况呢?也就是说,我们有可 

能覆盖一个内部类吗?这看起来似乎是一个非常有用的概念,但“覆盖”一个内部类——好象它是外部类的 

另一个方法——这一概念实际不能做任何事情:  

  

//: BigEgg。java  

// An inner class cannot be overriden   

// like a method  

  

class Egg {  

  protected class Yolk {  

    public Yolk() {  

      System。out。println(〃Egg。Yolk()〃);  



                                                                                          190 


…………………………………………………………Page 192……………………………………………………………

    }  

  }  

  private Yolk y;  

  public Egg() {  

    System。out。println(〃New Egg()〃);  

    y = new Yolk();  

  }  

}  

  

public class BigEgg extends Egg {  

  public class Yolk {  

    public Yolk() {  

      System。out。println(〃BigEgg。Yolk()〃);  

    }  

  }  

  public static void main(String'' args) {  

    new BigEgg();  

  }  

} ///:~  

  

默认构建器是由编译器自动合成的,而且会调用基础类的默认构建器。大家或许会认为由于准备创建一个 

BigEgg,所以会使用Yolk 的“被覆盖”版本。但实际情况并非如此。输出如下:  

New Egg()  

Egg。Yolk()  

这个例子简单地揭示出当我们从外部类继承的时候,没有任何额外的内部类继续下去。然而,仍然有可能 

 “明确”地从内部类继承:  

  

//: BigEgg2。java  

// Proper inheritance of an inner class  

  

class Egg2 {  

  protected class Yolk {  

    public Yolk() {  

      System。out。println(〃Egg2。Yolk()〃);  

    }  

    public void f() {  

      System。out。println(〃Egg2。Yolk。f()〃);  

    }  

  }  

  private Yolk y = new Yolk();  

  public Egg2() {  

    System。out。println(〃New Egg2()〃);  

  }  

  public void insertYolk(Yolk yy) { y = yy; }  

  public void g() { y。f(); }  

}  

  

public class BigEgg2 extends Egg2 {  

  public class Yolk extends Egg2。Yolk {  

    public Yolk() {  

      System。out。println(〃BigEgg2。Yolk()〃);  

    }  



                                                                                             191 


…………………………………………………………Page 193……………………………………………………………

    public void f() {  

      System。out。println(〃BigEgg2。Yolk。f()〃);  

    }  

  }  

  public BigEgg2() { insertYolk(new Yolk()); }  

  public static void main(String'' args) {  

    Egg2 e2 = new BigEgg2();  

    e2。g();  

  }  

} ///:~  

  

现在,BigEgg2。Yolk 明确地扩展了Egg2。Yolk,而且覆盖了它的方法。方法 insertYolk()允许BigEgg2 将它 

自己的某个Yolk 对象上溯造型至 Egg2 的y 句柄。所以当g()调用y。f()的时候,就会使用f()被覆盖版本。 

输出结果如下:  

Egg2。Yolk()  

New Egg2()  

Egg2。Yolk()  

BigEgg2。Yolk()  

BigEgg2。Yolk。f()  

对Egg2。Yolk()的第二个调用是BigEgg2。Yolk 构建器的基础类构建器调用。调用  

g()的时候,可发现使用的是f()的被覆盖版本。  



7。6。8  内部类标识符  



由于每个类都会生成一个。class 文件,用于容纳与如何创建这个类型的对象有关的所有信息(这种信息产生 

了一个名为 Class 对象的元类),所以大家或许会猜到内部类也必须生成相应的。class 文件,用来容纳与它 

们的Class 对象有关的信息。这些文件或类的名字遵守一种严格的形式:先是封装类的名字,再跟随一个, 

再跟随内部类的名字。例如,由 InheritInner。java创建的。class 文件包括:  

InheritInner。class  

WithInnerInner。class  

WithInner。class  

如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部类标识符使用。若内部类嵌套于其他 

内部类中,则它们的名字简单地追加在一个以及外部类标识符的后面。  

这种生成内部名称的方法除了非常简单和直观以外,也非常“健壮”,可适应大多数场合的要求(注释 

③)。由于它是Java 的标准命名机制,所以产生的文件会自动具备“与平台无关”的能力(注意Java 编译 

器会根据情况改变内部类,使其在不同的平台中能正常工作)。  

  

③:但在另一方面,由于“”也是Unix 外壳的一个元字符,所以有时会在列出。class 文件时遇到麻烦。对 

一家以Unix 为基础的公司——Sun——来说,采取这种方案显得有些奇怪。我的猜测是他们根本没有仔细考 

虑这方面的问题,而是认为我们会将全部注意力自然地放在源码文件上。  



7。6。9  为什么要用内部类:控制框架  



到目前为止,大家已接触了对内部类的运作进行描述的大量语法与概念。但这些并不能真正说明内部类存在 

的原因。为什么Sun 要如此麻烦地在Java 1。1 里添加这样的一种基本语言特性呢?答案就在于我们在这里要 

学习的“控制框架”。  

一个“应用程序框架”是指一个或一系列类,它们专门设计用来解决特定类型的问题。为应用应用程序框 

架,我们可从一个或多个类继承,并覆盖其中的部分方法。我们在覆盖方法中编写的代码用于定制由那些应 

用程序框架提供的常规方案,以便解决自己的实际问题。“控制框架”属于应用程序框架的一种特殊类型, 

受到对事件响应的需要的支配;主要用来响应事件的一个系统叫作“由事件驱动的系统”。在应用程序设计 

语言中,最重要的问题之一便是“图形用户界面”(GUI),它几乎完全是由事件驱动的。正如大家会在第 

13章学习的那样,Java 1。1 AWT 属于一种控制框架,它通过内部类完美地解决了GUI 的问题。  

为理解内部类如何简化控制框架的创建与使用,可认为一个控制框架的工作就是在事件“就绪”以后执行它 



                                                                              192 


…………………………………………………………Page 194……………………………………………………………

们。尽管“就绪”的意思很多,但在目前这种情况下,我们却是以计算机时钟为基础。随后,请认识到针对 

控制框架需要控制的东西,框架内并未包含任何特定的信息。首先,它是一个特殊的接口,描述了所有控制 

事件。它可以是一个抽象类,而非一个实际的接口。由于默认行为是根据时间控制的,所以部分实施细节可 

能包括:  

  

//: Event。java  

// The mon methods for any control event  

package c07。controller;  

  

abstract public class Event {  

  private long evtTime;  

  public Event(long eventTime) {  

    evtTime = eventTime;  

  }  

  public boolean ready() {  

    return System。currentTimeMillis() 》= evtTime;  

  }  

  abstract public void action();  

  abstract public String description();  

} ///:~  

  

希望Event (事件)运行的时候,构建器即简单地捕获时间。同时 ready()告诉我们何时该运行它。当然, 

ready()也可以在一个衍生类中被覆盖,将事件建立在除时间以外的其他东西上。  

action()是事件就绪后需要调用的方法,而 description()提供了与事件有关的文字信息。  

下面这个文件包含了实际的控制框架,用于管理和触发事件。第一个类实际只是一个“助手”类,它的职责 

是容纳Event 对象。可用任何适当的集合替换它。而且通过第 8 章的学习,大家会知道另一些集合可简化我 

们的工作,不需要我们编写这些额外的代码:  

  

//: Controller。java  

// Along with Event; the generic  

// framework for all control systems:  

package c07。controller;  

  

// This is just a way to hold Event objects。  

class EventSet {  

  private Event'' events = new Event'100';  

  private int index = 0;  

  private int next = 0;  

  public void add(Event e) {  

    if(index 》= events。length)  

      return; // (In real life; throw exception)  

    events'index++' = e;  

  }  

  public Event getNext() {  

    boolean looped = false;  

    int start = next;  

    do {  

      next = (next + 1) % events。length;  

      // See if it has looped to the beginning:  

      if(start == next) looped = true;  

      // If it loops past start; the list   

      // is empty:  



                                                  
返回目录 上一页 下一页 回到顶部 10 9
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!