用内嵌类减少JAVA程序设计中的混乱
摘 要:JDK1.1 中 最 有 争 议 的 变 化 也 许 就 是 引 入 了“ 内 嵌 类”。 争 议 的 根 源 在 于 大 多 数 人 认 为 内 嵌 类 将 改 变JAVA 语 言, 使 其 变 得 臃 肿。 然 而, 我 认 为 内 嵌 类 是 对JAVA 语 言 的 有 效 改 变, 并 且 对 现 存 的JAVA 虚 拟 机 没 有 任 何 影 响。
在1996 年12 月 那 期 名 为“ 总 述” 的 专 栏 中, 我 讨 论 了 在JAVA 中 构 造 一 般 集 合 类 的 困 难。 之 后 发 生 了 两 件 事: 一,SUN 建 立 了 内 嵌 类 以 帮 助 实 现JAVA BEANS; 二,SUN 为JAVA 提 出 了 一 个 基 本 的 集 合 类。 我 赞 同SUN 的 提 议, 但 我 希 望SUN 能 提 供 进 行 强 类 型 检 查 的 方 法。 强 类 型 检 查 是 一 种 确 保 放 到 某 个 集 合 中 的 对 象 是 同 类 型 的 机 制。 目 前, 这 些 集 合 把Object 类 当 作 基 本 的 引 用, 如 果 你 由 于 你 的 类 中 的 错 误 把 某 个 非 法 的 类 存 入 集 合, 那 么 当 你 从 集 合 中 搜 索 存 好 的 引 用 时, 上 述 引 用 就 会 产 生 类 强 制 性 异 常。 作 为 提 供 类 型 强 制 的 类 的 一 个 例 子, 参 看 下 面“ 资 源” 部 分 中 的“ContainerOrganizer” 类。
1 什 么 是 内 嵌 类
内 嵌 类 是 些 类 文 件, 它 们 的 名 字 被“ 限 定” 在 另 一 个 类 内。 所 谓 的“ 限 定” 就 是 定 义 了 它 什 么 时 候 能 被JAVA 代 码 引 用。 当 两 个 类 共 享 一 个 包 时, 它 们 就 有“ 包 限 定”。 除 非 包 被 定 义 为 公 用 包, 否 则 包 外 的 类 不 能 引 用 其 中 的 类。 这 样 类 名 就 限 定 在 包 中。
内 嵌 类 被 限 定 在 说 明 它 们 的 类 中, 实 际 上 对 同 一 个 包 中 的 其 他 类 是 不 可 见 的。 这 种 对 包 中 其 他 类 的 屏 蔽 使 程 序 员 能 在 包 含 类 中 建 立 类 集, 同 时 不 会 弄 乱 包 中 的 命 名 空 间。
根 据SUN 公 司 的 文 挡,SUN 的 工 程 师 们 认 为 内 嵌 类 解 决 了JAVA 定 义 中 的 一 个 问 题, 即:JAVA 类 只 能 被 定 义 为 与 别 的 类 同 等 的 类。 内 嵌 类 首 先 改 变 了 编 译 器, 为 了 支 持 与 现 存 虚 拟 机 的 向 后 兼 容, 内 嵌 类 实 际 上 被 编 译 成 正 常 的 类 文 件, 只 是 改 变 了 名 字 以 防 止 与 已 有 的 类 相 冲 突。
为 支 持 内 嵌 类, 在JAVA 中 对 应 该 在 程 序 的 何 处 说 明 类 的 有 关 规 定 作 了 修 改。 如 果 你 在JAVA1.0X 中 编 过 程 序, 你 就 会 知 道 能 在 单 个 源 文 件 中 定 义 多 个 类( 只 要 其 中 一 个 类 是 公 共 的), 并 且 第 二 个 以 及 随 后 的 类 必 须 紧 跟 在 第 一 个 类 的 花 括 号 后 面。 在1.1 中 这 些 都 改 变 了: 现 在 你 可 以 在 另 一 个 类 内 声 明 类。 下 面 的 例 子 说 明 得 更 清 楚。 在JAVA1.0 中 你 必 须 象 下 面 所 示 的 代 码 一 样 在 一 个 文 件 中 连 续 地 声 明 两 个 类:
public class MyPublicClass {
public MyPublicClass() { ... }
void method1() { ... }
int method2() { ... }
public method3(int x, int y) { ... }
}
上 面 的 类 是 存 放 在MyPublicClass.java 文 件 中 的“ 基 本 类”(base class)。 它 在method3 方 法 中 需 要 别 的 类 帮 助 它 完 成 某 些 功 能。 一 般 这 个 类 也 在 同 一 个 文 件 中 定 义, 下 面 即 是 一 例:
class MyHelperClass {
MyHelperClass() { ... }
int someHelperMethod(int z, int q) { ... }
}
同 一 个 文 件 中 的 类 在 某 些 方 面 通 常 是 相 互 关 联 的。 例 如, 我 们 在BinarySearchTree 类 中 建 立 了BSTEnumerator 类, 它 提 供 了 一 种 返 回 二 进 制 搜 索 树 中 所 有 元 素( 或 键) 的 方 法。 在Dictionary 类 的 子 类, 如Hashtable 中, 通 常 是 把 这 些 类 组 合 到 一 个 文 件 中。
有 了 内 嵌 类, 你 可 以 用 如 下 的 代 码 说 明 类:
public class MyPublicClass {
public MyPublicClass() { ... }
class MyHelperClass {
MyHelperClass() { ... }
int someHelperMethod(int z, int q) { ... }
}
void method1() { ... }
int method2() { ... }
public method3(int x, int y) { ... }
}
你 可 以 看 到, 这 只 涉 及 到 一 些 文 本 的 变 化。 然 而, 内 嵌 类 的 出 现 是 很 有 意 义 的。
上 面 例 子 中 的 内 嵌 类 是MyHelperClass, 但 在 第 一 种 情 况 下 有 两 个 类( 也 许 在 默 认 的 包 中):MyPublicClass 类 和 MyHelperClass 类。 在 第 二 个 例 子 中 仍 有 两 个 类, 除 了MypublicClass 类 以 外, 另 一 个 类 映 象 为MyPublicClass$MyHelperClass。 这 样 做 的 好 处 是 建 立 了 新 的 命 名 空 间, 它 只 有 帮 助 类 的 名 字。 在 前 一 个 例 子 中, 基 本 类 和 帮 助 类 都 占 用 了 命 名 空 间, 有 了 内 嵌 类 后, 帮 助 类 得 到 了 一 个 完 全 属 于 它 自 己 的 命 名 空 间。
2 如 何 使 用 内 嵌 类
我 们 以 BinarySearchTree 类 来 说 明 内 嵌 类 的 使 用。
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Dictionary;
public class BinarySearchTree extends Dictionary {
BSTNode rootNode;
private int elementCount;
private ContainerOrganizer co;
/**
* Define an inner class to be the enumerator
*/
class BSTEnumerator implements Enumeration {
private BSTNode currentNode;
private boolean keys;
BSTEnumerator(BSTNode start, boolean doKeys) {
super();
currentNode = (start != null) ? start.min() : null;
keys = doKeys;
}
public boolean hasMoreElements() {
return (currentNode != null);
}
public Object nextElement() {
if (currentNode == null)
throw new NoSuchElementException();
BSTNode n = currentNode;
currentNode = n.successor();
return (keys ? n.key : n.payload);
}
}
当 在JDK1.1 中 编 译 上 面 的 类 时, 编 译 器 建 立 两 个 类 文 件:BinarySearchTree.class 类 文 件 和BinarySearchTree$BSTEnumerator.class 类 文 件。 注 意 类 名 中 有“$” 字 符, 它 把 内 嵌 类 从 包 含 它 的 基 本 类 中 区 别 开 来。 在 内 嵌 类BSTEnumerator 的 定 义 后 继 续 定 义BinarySearchTree:
public BinarySearchTree(ContainerOrganizer c) {
super();
co = c;
co.setDict(this);
}
... and so on for the rest of the class ...
}
使 用 这 种 形 式 的 内 嵌 类 的 主 要 价 值 是 能 建 立 特 别 指 定 的 枚 举 类, 并 且 不 会 在 包 这 一 级 弄 乱 命 名 空 间。 考 虑 到 集 合 类 组 成 的 包 可 能 有 许 多 不 同 的 基 于 行 为 的 返 回 值( 如 枚 举, 有 序 列 表, 固 定 的 等 等), 具 体 的 类 的 数 目 可 能 很 大。 许 多 名 字 以 前 只 需 一 个 单 一 的 值。 当 然 完 全 可 以 用 手 工 来 完 成 这 些 命 名 空 间 的 维 护 工 作, 实 际 效 果 和 内 嵌 类 一 样; 这 也 就 是 为 什 么SUN 声 明 它 只 是 编 译 器 中 的 语 法 修 饰。
3 一 种 内 嵌 类: 匿 名 类
当 你 只 是 想 传 递 一 个 做 些 事 情 的 方 法 时 你 甚 至 不 需 名 字( 类 似 于C 语 言 的 回 调 函 数), 你 只 需 建 立 一 个 匿 名 的 内 嵌 类。 这 些 类 只 是 没 有 具 体 名 字 的 内 嵌 类。 只 用 一 次 的 类 一 般 不 命 名。
为 说 明 匿 名 类, 我 们 编 写 了 BinarySearchTree 的 keys 和 elements 方 法, 它 们 都 用 来 返 回 一 个BSTEnumerator 对 象, 如 下 所 示。
private Enumeration doEnumerate(final boolean k) {
return new Enumeration() {
private BSTNode currentNode = rootNode;
private boolean keys = k;
public boolean hasMoreElements() {
return (currentNode != null);
}
public Object nextElement() {
if (currentNode == null)
throw new NoSuchElementException();
BSTNode n = currentNode;
currentNode = n.successor();
return (keys ? n.key : n.payload);
}
};
}
public Enumeration elements() {
return doEnumerate(false);
}
public Enumeration keys() {
return doEnumerate(true);
}
仔 细 看 一 下doEnumerate 函 数; 有 两 样 事 情 比 较 奇 怪。 第 一, 它 返 回 接 口 的 新 的 实 例; 其 次, 它 前 面 的 参 数k 是final 型 的。 到 底 是 怎 么 回 事 呢 ? 答 案 是 这 种 技 术 是 使 用 内 嵌 类 的 又 一 方 法。
返 回 说 明 看 起 来 是 建 立 了 一 个 新 的 接 口 实 例, 但 事 实 上 是 建 立 了 一 个 没 有 明 确 名 字 并 且 实 现 了 接 口 的 新 类。 在 这 种 情 况 下, 实 际 上 的 名 字 是BinarySearchTree$1, 但 它 只 对 编 译 器 的 作 者 有 意 义。
用 这
补充:软件开发 , Java ,