Java编程思想第4版[中文版](PDF格式)-第55部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 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……………………………………………………………