⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter7.htm

📁 一本Java由初级到高级的编程书籍
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<br>
288页程序<br>
<br>
在实例初始化模块中,我们可看到代码不能作为类初始化模块(即if语句)的一部分执行。所以实际上,一个实例初始化模块就是一个匿名内部类的构建器。当然,它的功能是有限的;我们不能对实例初始化模块进行过载处理,所以只能拥有这些构建器的其中一个。<br>
<br>
7.6.3 链接到外部类<br>
迄今为止,我们见到的内部类好象仅仅是一种名字隐藏以及代码组织方案。尽管这些功能非常有用,但似乎并不特别引人注目。然而,我们还忽略了另一个重要的事实。创建自己的内部类时,那个类的对象同时拥有指向封装对象(这些对象封装或生成了内部类)的一个链接。所以它们能访问那个封装对象的成员——毋需取得任何资格。除此以外,内部类拥有对封装类所有元素的访问权限(注释②)。下面这个例子阐示了这个问题:<br>
<br>
289-290页程序<br>
<br>
②:这与C++“嵌套类”的设计颇有不同,后者只是一种单纯的名字隐藏机制。在C++中,没有指向一个封装对象的链接,也不存在默认的访问权限。<br>
<br>
其中,Sequence只是一个大小固定的对象数组,有一个类将其封装在内部。我们调用add(),以便将一个新对象添加到Sequence末尾(如果还有地方的话)。为了取得Sequence中的每一个对象,要使用一个名为Selector的接口,它使我们能够知道自己是否位于最末尾(end()),能观看当前对象(current() 
Object),以及能够移至Sequence内的下一个对象(next() Object)。由于Selector是一个接口,所以其他许多类都能用它们自己的方式实现接口,而且许多方法都能将接口作为一个自变量使用,从而创建一般的代码。<br>
在这里,SSelector是一个私有类,它提供了Selector功能。在main()中,大家可看到Sequence的创建过程,在它后面是一系列字串对象的添加。随后,通过对getSelector()的一个调用生成一个Selector。并用它在Sequence中移动,同时选择每一个项目。<br>
从表面看,SSelector似乎只是另一个内部类。但不要被表面现象迷惑。请注意观察end(),current()以及next(),它们每个方法都引用了o。o是个不属于SSelector一部分的句柄,而是位于封装类里的一个private字段。然而,内部类可以从封装类访问方法与字段,就象已经拥有了它们一样。这一特征对我们来说是非常方便的,就象在上面的例子中看到的那样。<br>
因此,我们现在知道一个内部类可以访问封装类的成员。这是如何实现的呢?内部类必须拥有对封装类的特定对象的一个引用,而封装类的作用就是创建这个内部类。随后,当我们引用封装类的一个成员时,就利用那个(隐藏)的引用来选择那个成员。幸运的是,编译器会帮助我们照管所有这些细节。但我们现在也可以理解内部类的一个对象只能与封装类的一个对象联合创建。在这个创建过程中,要求对封装类对象的句柄进行初始化。若不能访问那个句柄,编译器就会报错。进行所有这些操作的时候,大多数时候都不要求程序员的任何介入。<br>
<br>
7.6.4 static内部类<br>
为正确理解static在应用于内部类时的含义,必须记住内部类的对象默认持有创建它的那个封装类的一个对象的句柄。然而,假如我们说一个内部类是static的,这种说法却是不成立的。static内部类意味着:<br>
(1) 为创建一个static内部类的对象,我们不需要一个外部类对象。<br>
(2) 不能从static内部类的一个对象中访问一个外部类对象。<br>
但在存在一些限制:由于static成员只能位于一个类的外部级别,所以内部类不可拥有static数据或static内部类。<br>
倘若为了创建内部类的对象而不需要创建外部类的一个对象,那么可将所有东西都设为static。为了能正常工作,同时也必须将内部类设为static。如下所示:<br>
<br>
291-292页程序<br>
<br>
在main()中,我们不需要Parcel10的对象;相反,我们用常规的语法来选择一个static成员,以便调用将句柄返回Contents和Destination的方法。<br>
通常,我们不在一个接口里设置任何代码,但static内部类可以成为接口的一部分。由于类是“静态”的,所以它不会违反接口的规则——static内部类只位于接口的命名空间内部:<br>
<br>
292页中程序<br>
<br>
在本书早些时候,我建议大家在每个类里都设置一个main(),将其作为那个类的测试床使用。这样做的一个缺点就是额外代码的数量太多。若不愿如此,可考虑用一个static内部类容纳自己的测试代码。如下所示:<br>
<br>
292-293页程序<br>
<br>
这样便生成一个独立的、名为TestBed$Tester的类(为运行程序,请使用“java 
TestBed$Tester”命令)。可将这个类用于测试,但不需在自己的最终发行版本中包含它。<br>
<br>
7.6.5 引用外部类对象<br>
若想生成外部类对象的句柄,就要用一个点号以及一个this来命名外部类。举个例子来说,在Sequence.SSelector类中,它的所有方法都能产生外部类Sequence的存储句柄,方法是采用Sequence.this的形式。结果获得的句柄会自动具备正确的类型(这会在编译期间检查并核实,所以不会出现运行期的开销)。<br>
有些时候,我们想告诉其他某些对象创建它某个内部类的一个对象。为达到这个目的,必须在new表达式中提供指向其他外部类对象的一个句柄,就象下面这样:<br>
<br>
293-294页程序<br>
<br>
为直接创建内部类的一个对象,不能象大家或许猜想的那样——采用相同的形式,并引用外部类名Parcel11。此时,必须利用外部类的一个对象生成内部类的一个对象:<br>
Parcel11.Contents c = p.new Contents();<br>
因此,除非已拥有外部类的一个对象,否则不可能创建内部类的一个对象。这是由于内部类的对象已同创建它的外部类的对象“默默”地连接到一起。然而,如果生成一个static内部类,就不需要指向外部类对象的一个句柄。<br>
<br>
7.6.6 从内部类继承<br>
由于内部类构建器必须同封装类对象的一个句柄联系到一起,所以从一个内部类继承的时候,情况会稍微变得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种关联:<br>
<br>
294-295页程序<br>
<br>
从中可以看到,InheritInner只对内部类进行了扩展,没有扩展外部类。但在需要创建一个构建器的时候,默认对象已经没有意义,我们不能只是传递封装对象的一个句柄。此外,必须在构建器中采用下述语法:<br>
enclosingClassHandle.super();<br>
它提供了必要的句柄,以便程序正确编译。<br>
<br>
7.6.7 内部类可以覆盖吗?<br>
若创建一个内部类,然后从封装类继承,并重新定义内部类,那么会出现什么情况呢?也就是说,我们有可能覆盖一个内部类吗?这看起来似乎是一个非常有用的概念,但“覆盖”一个内部类——好象它是外部类的另一个方法——这一概念实际不能做任何事情:<br>
<br>
295-296页程序<br>
<br>
默认构建器是由编译器自动合成的,而且会调用基础类的默认构建器。大家或许会认为由于准备创建一个BigEgg,所以会使用Yolk的“被覆盖”版本。但实际情况并非如此。输出如下:<br>
New Egg()<br>
Egg.Yolk()<br>
这个例子简单地揭示出当我们从外部类继承的时候,没有任何额外的内部类继续下去。然而,仍然有可能“明确”地从内部类继承:<br>
<br>
296-297页程序<br>
<br>
现在,BigEgg2.Yolk明确地扩展了Egg2.Yolk,而且覆盖了它的方法。方法insertYolk()允许BigEgg2将它自己的某个Yolk对象上溯造型至Egg2的y句柄。所以当g()调用y.f()的时候,就会使用f()被覆盖版本。输出结果如下:<br>
Egg2.Yolk()<br>
New Egg2()<br>
Egg2.Yolk()<br>
BigEgg2.Yolk()<br>
BigEgg2.Yolk.f()<br>
对Egg2.Yolk()的第二个调用是BigEgg2.Yolk构建器的基础类构建器调用。调用<br>
g()的时候,可发现使用的是f()的被覆盖版本。<br>
<br>
7.6.8 内部类标识符<br>
由于每个类都会生成一个.class文件,用于容纳与如何创建这个类型的对象有关的所有信息(这种信息产生了一个名为Class对象的元类),所以大家或许会猜到内部类也必须生成相应的.class文件,用来容纳与它们的Class对象有关的信息。这些文件或类的名字遵守一种严格的形式:先是封装类的名字,再跟随一个$,再跟随内部类的名字。例如,由InheritInner.java创建的.class文件包括:<br>
InheritInner.class<br>
WithInner$Inner.class<br>
WithInner.class<br>
如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部类标识符使用。若内部类嵌套于其他内部类中,则它们的名字简单地追加在一个$以及外部类标识符的后面。<br>
这种生成内部名称的方法除了非常简单和直观以外,也非常“健壮”,可适应大多数场合的要求(注释③)。由于它是Java的标准命名机制,所以产生的文件会自动具备“与平台无关”的能力(注意Java编译器会根据情况改变内部类,使其在不同的平台中能正常工作)。<br>
<br>
③:但在另一方面,由于“$”也是Unix外壳的一个元字符,所以有时会在列出.class文件时遇到麻烦。对一家以Unix为基础的公司——Sun——来说,采取这种方案显得有些奇怪。我的猜测是他们根本没有仔细考虑这方面的问题,而是认为我们会将全部注意力自然地放在源码文件上。<br>
<br>
7.6.9 为什么要用内部类:控制框架<br>
到目前为止,大家已接触了对内部类的运作进行描述的大量语法与概念。但这些并不能真正说明内部类存在的原因。为什么Sun要如此麻烦地在Java 
1.1里添加这样的一种基本语言特性呢?答案就在于我们在这里要学习的“控制框架”。<br>
一个“应用程序框架”是指一个或一系列类,它们专门设计用来解决特定类型的问题。为应用应用程序框架,我们可从一个或多个类继承,并覆盖其中的部分方法。我们在覆盖方法中编写的代码用于定制由那些应用程序框架提供的常规方案,以便解决自己的实际问题。“控制框架”属于应用程序框架的一种特殊类型,受到对事件响应的需要的支配;主要用来响应事件的一个系统叫作“由事件驱动的系统”。在应用程序设计语言中,最重要的问题之一便是“图形用户界面”(GUI),它几乎完全是由事件驱动的。正如大家会在第13章学习的那样,Java 
1.1 AWT属于一种控制框架,它通过内部类完美地解决了GUI的问题。<br>
为理解内部类如何简化控制框架的创建与使用,可认为一个控制框架的工作就是在事件“就绪”以后执行它们。尽管“就绪”的意思很多,但在目前这种情况下,我们却是以计算机时钟为基础。随后,请认识到针对控制框架需要控制的东西,框架内并未包含任何特定的信息。首先,它是一个特殊的接口,描述了所有控制事件。它可以是一个抽象类,而非一个实际的接口。由于默认行为是根据时间控制的,所以部分实施细节可能包括:<br>
<br>
299页上程序<br>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -