友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第36部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
179
…………………………………………………………Page 181……………………………………………………………
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
}
public static void main(String'' args) {
Parcel2 p = new Parcel2();
p。ship(〃Tanzania〃);
Parcel2 q = new Parcel2();
// Defining handles to inner classes:
Parcel2。Contents c = q。cont();
Parcel2。Destination d = q。to(〃Borneo〃);
}
} ///:~
若想在除外部类非 static 方法内部之外的任何地方生成内部类的一个对象,必须将那个对象的类型设为“外
部类名。内部类名”,就象main()中展示的那样。
7。6。1 内部类和上溯造型
迄今为止,内部类看起来仍然没什么特别的地方。毕竟,用它实现隐藏显得有些大题小做。Java 已经有一个
非常优秀的隐藏机制——只允许类成为“友好的”(只在一个包内可见),而不是把它创建成一个内部类。
然而,当我们准备上溯造型到一个基础类(特别是到一个接口)的时候,内部类就开始发挥其关键作用(从
用于实现的对象生成一个接口句柄具有与上溯造型至一个基础类相同的效果)。这是由于内部类随后可完全
进入不可见或不可用状态——对任何人都将如此。所以我们可以非常方便地隐藏实施细节。我们得到的全部
回报就是一个基础类或者接口的句柄,而且甚至有可能不知道准确的类型。就象下面这样:
//: Parcel3。java
// Returning a handle to an inner class
package c07。parcel3;
abstract class Contents {
abstract public int value();
}
interface Destination {
String readLabel();
}
public class Parcel3 {
private class PContents extends Contents {
private int i = 11;
public int value() { return i; }
180
…………………………………………………………Page 182……………………………………………………………
}
protected class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}
class Test {
public static void main(String'' args) {
Parcel3 p = new Parcel3();
Contents c = p。cont();
Destination d = p。dest(〃Tanzania〃);
// Illegal …can't access private class:
//! Parcel3。PContents c = p。new PContents();
}
} ///:~
现在,Contents 和Destination 代表可由客户程序员使用的接口(记住接口会将自己的所有成员都变成
public 属性)。为方便起见,它们置于单独一个文件里,但原始的 Contents 和Destination 在它们自己的
文件中是相互public 的。
在Parcel3 中,一些新东西已经加入:内部类PContents 被设为 private,所以除了Parcel3 之外,其他任
何东西都不能访问它。PDestination 被设为 protected,所以除了 Parcel3,Parcel3 包内的类(因为
protected 也为包赋予了访问权;也就是说,protected 也是“友好的”),以及Parcel3 的继承者之外,其
他任何东西都不能访问 PDestination。这意味着客户程序员对这些成员的认识与访问将会受到限制。事实
上,我们甚至不能下溯造型到一个 private 内部类(或者一个protected 内部类,除非自己本身便是一个继
承者),因为我们不能访问名字,就象在 classTest 里看到的那样。所以,利用private 内部类,类设计人
员可完全禁止其他人依赖类型编码,并可将具体的实施细节完全隐藏起来。除此以外,从客户程序员的角度
来看,一个接口的范围没有意义的,因为他们不能访问不属于公共接口类的任何额外方法。这样一来,Java
编译器也有机会生成效率更高的代码。
普通(非内部)类不可设为private 或 protected——只允许 public 或者“友好的”。
注意Contents 不必成为一个抽象类。在这儿也可以使用一个普通类,但这种设计最典型的起点依然是一个
“接口”。
7。6。2 方法和作用域中的内部类
至此,我们已基本理解了内部类的典型用途。对那些涉及内部类的代码,通常表达的都是“单纯”的内部
类,非常简单,且极易理解。然而,内部类的设计非常全面,不可避免地会遇到它们的其他大量用法——假
若我们在一个方法甚至一个任意的作用域内创建内部类。有两方面的原因促使我们这样做:
(1) 正如前面展示的那样,我们准备实现某种形式的接口,使自己能创建和返回一个句柄。
(2) 要解决一个复杂的问题,并希望创建一个类,用来辅助自己的程序方案。同时不愿意把它公开。
在下面这个例子里,将修改前面的代码,以便使用:
(1) 在一个方法内定义的类
181
…………………………………………………………Page 183……………………………………………………………
(2) 在方法的一个作用域内定义的类
(3) 一个匿名类,用于实现一个接口
(4) 一个匿名类,用于扩展拥有非默认构建器的一个类
(5) 一个匿名类,用于执行字段初始化
(6) 一个匿名类,通过实例初始化进行构建(匿名内部类不可拥有构建器)
所有这些都在 innerscopes 包内发生。首先,来自前述代码的通用接口会在它们自己的文件里获得定义,使
它们能在所有的例子里使用:
//: Destination。java
package c07。innerscopes;
interface Destination {
String readLabel();
} ///:~
由于我们已认为Contents 可能是一个抽象类,所以可采取下面这种更自然的形式,就象一个接口那样:
//: Contents。java
package c07。innerscopes;
interface Contents {
int value();
} ///:~
尽管是含有具体实施细节的一个普通类,但Wrapping 也作为它所有衍生类的一个通用“接口”使用:
//: Wrapping。java
package c07。innerscopes;
public class Wrapping {
private int i;
public Wrapping(int x) { i = x; }
public int value() { return i; }
} ///:~
在上面的代码中,我们注意到 Wrapping 有一个要求使用自变量的构建器,这就使情况变得更加有趣了。
第一个例子展示了如何在一个方法的作用域(而不是另一个类的作用域)中创建一个完整的类:
//: Parcel4。java
// Nesting a class within a method
package c07。innerscopes;
public class Parcel4 {
public Destination dest(String s) {
class PDestination
implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
182
…………………………………………………………Page 184……………………………………………………………
}
return new PDestination(s);
}
public static void main(String'' args) {
Parcel4 p = new Parcel4();
Destination d = p。dest(〃Tanzania〃);
}
} ///:~
PDestination 类属于 dest()的一部分,而不是 Parcel4 的一部分(同时注意可为相同目录内每个类内部的一
个内部类使用类标识符 PDestination,这样做不会发生命名的冲突)。因此,PDestination 不可从 dest()
的外部访问。请注意在返回语句中发生的上溯造型——除了指向基础类Destination 的一个句柄之外,没有
任何东西超出dest()的边界之外。当然,不能由于类PDestination 的名字置于 dest()内部,就认为在
dest()返回之后 PDestination 不是一个有效的对象。
下面这个例子展示了如何在任意作用域内嵌套一个内部类:
//: Parcel5。java
// Nesting a class within a scope
package c07。innerscopes;
public class Parcel5 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip(〃slip〃);
String s = ts。getSlip();
}
// Can't use it here! Out of scope:
//! TrackingSlip ts = new TrackingSlip(〃x〃);
}
public void track() { internalTracking(true); }
public static void main(String'' args) {
Parcel5 p = new Parcel5();
p。track();
}
} ///:~
TrackingSlip 类嵌套于一个 if语句的作用域内。这并不意味着类是有条件创建的——它会随同其他所有东
西得到编译。然而,在定义它的那个作用域之外,它是不可使用的。除这些以外,它看起来和一个普通类并
没有什么区别。
下面这个例子看起来有些奇怪:
//: Parcel6。java
// A method that returns an anonymous inner class
package c07。innerscopes;
183
…………………………………………………………Page 185……………………………………………………………
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String'' args) {
Parcel6 p = new Parcel6();
Contents c = p。cont();
}
} ///:~
cont()方法同时合并了返回值的创建代码,以及用于表示那个返回值的类。除此以外,这个类是匿名的——
它没有名字。而且看起来似乎更让人摸不着头脑的是,我们准备创建一个 Contents 对象:
return new Contents()
但在这之后,在遇到分号之前,我们又说:“等一等,让我先在一个类定义里再耍一下花招”:
return new Contents() {
private int i = 11;
public int value() { return i; }
};
这种奇怪的语法要表达的意思是:“创建从 Contents 衍生出来的匿名类的一个对象”。由 new 表达式返回的
句柄会自动上溯造型成一个Contents 句柄。匿名内部类的语法其实要表达的是:
class MyContents extends Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();
在匿名内部类中,Contents 是用一个默认构建器创建的。下面这段代码展示了基础类需要含有自变量的一个
构建器时做的事情:
//: Parcel7。java
// An anonymous inner class that calls the
// base…class constructor
package c07。innerscopes;
public class Parcel7 {
public Wrapping wrap(int x) {
// Base constructor call:
return new Wrapping(x) {
public int value() {
return super。value() * 47;
}
}; // Semicolon required
}
public static void main(String'' args) {
Parcel7 p = new Parcel7();
Wrapping w = p。wrap(10);
184
…………………………………………………………Page 186……………………………………………………………
}
} ///:~
也就是说,我们将适当的自变量简单地传递给基础类构建器,在这儿表现为在“new Wrapping(x)”中传递
x。匿名类不能拥有一个构建器,这和在调用 super()时的常规做法不同。
在前述的两个例子中,分号并不标志着类主体的结束(和 C++不同)。相反,它标志着用于包含匿名类的那
个表达式的结束。因此,它完全等价于在其他任何地方使用分号。
若想对匿名内部类的一个对象进行某种形式的初始化,此时会出现什么情况呢?由于它是匿名的,没有名字
赋给构建器,所以我们不能拥有一个构建器。然而,我们可在定义自己的字段时进行初始化:
//: Parcel8。java
// An anonymous inner class that performs
// initialization。 A briefer version
// of Parcel5。java。
package c07。innerscopes;
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String'' args) {
Parcel8 p = new Parcel8();
Destination d = p。dest(〃Tanzania〃);
}
} ///:~
若试图定义一个匿名内部类,并想使用在匿名内部类外部定义的一个对象,则编译器要求外部对象为final
属性。这正是我们将dest()的自变量设为final 的原因。如果忘记这样做,就会得到一条编译期出错提示。
只要自己只是想分配一个字段,上述方法就肯定可行。但假如需要采取一些类似于构建器的行动,又应怎样
操作呢?通过Java 1。1 的实例初始化,我们可以有效地为一个匿名内部类创建一个构建器:
//: Parcel9。java
// Using 〃instance initialization〃 to perform
// construction on an anonymous inner class
package c07。innerscopes;
public class Parcel9 {
public Destination
dest(final String dest; final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math。round(price);
if(cost 》 100)
System。out。println(〃Over budget!〃);
}
185
…………………………………………………………Page 187……………………………………………………………
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String'' args) {
Parcel9 p = new Parcel9();
Destination d = p。dest(〃Tanzania〃; 101。395F);
}
} ///:~
在实例初始化模块中,我们可看到代码不能作为类初始化模块(即 if语句)的一部分执行。所以实际上,一
个实例初始化模块就是一个匿名内部类的构建器。当然,它的功能是有限的;我们不能对实例初始化模块进
行过载处理,所以只能拥有这些构建器的其中一个。
7。6。3 链接到外部类
迄今为止,我们见到的内部类好象仅仅是一种名字隐藏以及代码组织方案。尽管这些功能非常有用,但似乎
并不特别引人注目。然而,我们还忽略了另一个重要的事实。创建自己的内部类时,那个类的对象同时拥有
指向封装对象(这些对象封装或生成了内部类)的一个链接。所以它们能访问那个封装对象的成员——毋需
取得任何资格。除此以外,内部类拥有对封装类所有元素的访问权限(注释②)。下面这个例子阐示了这个
问题:
//: Sequence。java
// Holds a sequence of Objects
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object'' o;
private int next = 0;
public Sequence(int size) {
o = new Object'size';
}
public void add(Object x) {
if(next 《 o。length) {
o'next' = x;
next++;
}
}
private class SSelector implements Selector {
int i = 0;
public boolean end() {
return i == o。length;
}
public Object current() {
return o'i';
}
public void next() {
if(i 《 o。length) i++;
186
…………………………………………………………Page 188……………………………………………………………
}
}
public Selector getSelector() {
return new SSelector();
}
public static void main(String'' args) {
Sequence s = new Sequence(10);
for(int i = 0; i 《 10; i++)
s。add(Integer。toString(i));
Selector sl = s。getSelector();
w
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!