45fan.com - 路饭网

搜索: 您的位置主页 > 电脑频道 > 电脑教程 > 阅读资讯:内嵌类简介知识

内嵌类简介知识

2016-09-02 09:01:59 来源:www.45fan.com 【

内嵌类简介知识

用 内 嵌 类 减 少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, 但 它 只 对 编 译 器 的 作 者 有 意 义。

用 这 种 方 法 建 立 类 的 一 个 局 限 是, 新 的 类 不 能 定 义 构 造 操 作( 记 住: 它 没 有 名 字), 因 此 在 类 中 必 须 有 办 法 设 置 初 始 状 态。JAVA1.1 的 办 法 是 重 新 定 义 对 非 类 变 量 的 操 作。 尤 其 是 现 在 能 合 法 地 把 它 们 象 静 态 变 量 一 样 初 始 化, 却 不 用 把 它 们 放 在 静 态 初 始 化 块 中。 这 对 程 序 员 来 说 意 味 着 你 能 说 明 非 静 态 的 并 且 预 先 初 始 化 的 实 例 的 值, 例 如“int foo=10;”。

当 然, 当 你 创 建 内 嵌 类 时, 一 般 你 喜 欢 从 包 含 在enclosing 类 的 信 息 中 初 始 化 内 嵌 类。( 在 例 子 中,BinarySearchTree 就 是 包 容 类。) 这 是 通 过 这 样 一 些 规 则 完 成 的: 如 果 说 明 在 定 义 类 的 地 方, 那 么 内 嵌 类 能 访 问 所 说 明 所 有 的 字 段( 变 量)。 这 样 在 上 例 中, 初 始 化“currentNode=rootNode” 是 指 包 容 类 变 量 中 的 实 例 变 量rootNode。

在 上 面 的 匿 名 类 中 有 第 二 个 初 始 化:“keys=k;”。C 程 序 员 看 到 它 立 即 会 警 觉 告:“ 你 在 使 用 返 回 值 堆 栈 中 的 值 !” 注 意,k 在 这 段 代 码 中 是 正 式 与doEnumerate 相 关 的 参 数。 这 就 是“final” 关 键 字 新 语 义 的 原 因 所 在。

在JAVA 语 言 中, 变 量 的final 属 性 的 含 义 是, 它 的 值 只 能 分 配 一 次。 因 此, 一 旦 分 配 了 后,final 变 量 的 值 就 不 能 再 变。 在 上 面 的 例 子 中 编 译 器 可 以 安 全 地 把k 的 值 拷 贝 到 固 定 的 存 储 区 中。

JAVA 是“ 值 调 用” 语 言, 意 思 是 方 法 的 所 有 参 数 都 有 值, 值 的 改 变 不 会 影 响 该 值 的 调 用 方 法 拷 贝。 如 果 将 对 象 引 用 也 作 为 值 来 对 待, 这 种 语 义 就 显 得 有 些 模 糊, 并 且 如 果 传 递 了 对 象 的 值 后 又 修 改 了 它, 所 有 有 同 样 引 用 的 类 都 会 找 到 修 改 后 的 对 象。 虽 然 这 看 起 来 有 些 混 乱, 但JAVA 的 语 义 实 际 是 一 致 的。

匿 名 内 嵌 类 有 这 样 一 个 问 题: 当k 的 值 改 变 时 随 着k 的 分 配 会 有 什 么 变 化 ? 考 虑 一 下 下 面 这 段 混 乱 的 代 码:

   private Enumeration doEnumerate(boolean k) {
    Enumeration ee = new Enumeration() { 
      private BSTNode currentNode = rootNode;
      private boolean keys = k;
      public boolean hasMoreElements() { ... }
      public Object nextElement() { ... }
    };
    k = !k;
    return ee;
  }

上 面k 的 值 在 返 回ee 之 前 被 修 改。 虽 然 现 在 能 等 到 需 要 时 再 建 立 对 象 实 例, 但 如 何 确 定 使 用 了 哪 个k 值 ? 是 执 行new 时 的k 值, 还 是 执 行return 时 的k 值 ? 回 答 是 你 没 法 确 定, 因 而JAVA 软 件 工 程 师 们 规 定k 的 值 必 须 声 明 为final, 编 译 器 就 会 强 制k 的 值 不 变。 当k 的 值 被 说 明 为final 后, 编 译 器 就 会 忽 略 改 变 其 值 的 说 明, 决 定k 值 的 语 义 就 不 再 是 模 棱 两 可 的 了。

4 关 于“this”

当 你 写JAVA 代 码 时, 有 时 不 可 避 免 地 要 明 确 地 引 用 定 义 为 目 前 对 象 实 例 的 实 例 变 量。 你 解 决 的 办 法 是 在 变 量 名 前 加 一 个“this” 限 定 词。this.x 这 个 名 字 就 是 指 对 象 的this 实 例 中 的 变 量 名。 很 显 然, 如 果 你 在 内 嵌 类 中 引 用this, 它 是 指 内 嵌 类 本 身 所 定 义 的 域 或 变 量。 然 而, 我 们 刚 刚 解 决 了 使 用 包 容 类 — —rootNode — — 的 实 例 变 量 的 声 明, 如 果 我 们 只 需 在 内 嵌 类 中 定 义 一 个 名 字 也 是rootNode 的 变 量, 我 们 如 何 区 分 它 们 呢 ? 答 案 是 还 有 另 外 一 个 新 的this: qualified this。

关 于this 关 键 词 有 一 个 限 定 词。 该 限 定 词 的 定 义 是 你 可 用 任 何 一 个 包 容 类 的 名 字 作 为this 的 限 定 词。 在 我 们 假 设 的 定 义 其 自 己 的rootNode 域 的 内 嵌 类 这 一 例 子 中, 内 嵌 类 的 值 通 常 是this.rootNode, 包 容 类 中 的 值 能 用BinarySearchTree.this.rootNode 来 调 用。 你 可 以 看 到 这 个 名 字 始 终 明 确, 因 为JAVA1.1 新 规 定 内 嵌 类 不 能 与 它 的 任 何 一 个 包 容 类 同 名。

你 很 容 易 搞 混 到 底 在 引 用 哪 个 类 变 量, 我 建 议 你 保 持 你 的 实 例 变 量 有 明 确 的 名 字。 幸 运 的 是, 只 有 编 译 器 的 设 计 者 才 必 须 知 道 给 本 身 是 匿 名 内 嵌 类 的 包 容 类 中 的 实 例 变 量 取 了 什 么 名 字。

5 另 外 一 些 要 考 虑 的 问 题

JAVA 语 言 新 用 户 们 经 常 碰 到 的 问 题 是 缺 乏JAVA 方 法 调 用 的 引 用 传 递 语 义。 解 决 如 何 获 得 引 用 传 递 语 义 这 一 问 题 的 方 法 是 建 立 指 向 一 个 可 设 置 的, 并 且 以 后 可 以 检 索 的 值 的 类。 这 样 做 通 常 很 麻 烦, 因 为 必 须 建 立 一 个 完 整 的 类 才 能 从 方 法 调 用 中 返 回 多 个 值。

考 虑 一 下 下 面 这 段 代 码 中 内 嵌 类 是 如 何 解 决 这 个 问 题 的:

  public class PointSample {
  public void samplePoint(IntSetter x, IntSetter y, FloatSetter m) {
    x.set(10);
    y.set(100);
    m.set(5.5f);
   }
  }

上 面 的 代 码 要 返 回 三 个 值:x,y, 和m。 因 此 我 们 没 有 传 递 局 部 类 型, 而 是 用IntSetter 或FloatSetter 类 型 的 对 象 引 用。IntSetter 的 定 义 如 下:

  public interface IntSetter {
   public void set(int value);
   }

你 可 以 看 到, 这 只 是 定 义 了set 方 法 的 接 口, 该 方 法 选 取 一 个 整 数 值 并 把 它 设 置 好。FloatSetter 的 设 计 留 给 读 者 作 练 习。 现 在 考 虑 一 下 下 面 的 程 序 例 子:

 public class example {
  int     xPos = 0, yPos = 0;
  float    magnitude = 1.0f;
  public void doit() {
   PointSample ps = new PointSample();
   System.out.println("Shows off an anonymous inner class.");
   System.out.println("X,Y = ["+xPos+", "+yPos+"], Mag = "+magnitude);
   ps.samplePoint(new IntSetter() {public void set(int a) { xPos = a; }},
     new IntSetter() { public void set(int a) { yPos = a; }},
     new FloatSetter() { public void set(float a) { magnitude = a;}});
   System.out.println("X,Y = ["+xPos+", "+yPos+"], Mag = "+magnitude);
 }

 public static void main(String args[]) {
   example ex = new example();
   ex.doit();
  }
 }

main 方 法 所 做 的 是 建 立example 对 象 的 一 个 实 例, 并 且 调 用 其 中 的doit 方 法。 doit 方 法 更 有 趣: 它 建 立 了 一 个PointSample 对 象, 并 用 三 个 内 嵌 类 调 用 其samplePoint 方 法。

内 嵌 类 的 实 现 允 许 起 用samplePoint 方 法 简 单 地 把 结 果 存 到 调 用 类( 这 儿 是example) 的 实 例 变 量 中。 这 项 技 术 省 去 了 返 回 含 有 值 的 类 的 中 间 步 骤, 它 把 类 中 的 值 去 除, 并 存 到 实 例 变 量 中。 这 个 例 子 中 实 现 的 功 能 有 点 象 引 用 传 递, 用JAVA 的 术 语 来 说 意 思 是 它 传 递 了 一 个 能 改 变 引 用 对 象 的 状 态 的 操 作。 一 旦 你 掌 握 了 它 的 诀 窍 后, 你 会 发 现 内 嵌 类 带 来 了 无 穷 无 尽 的 可 能 性。

6 结 束 语

内 嵌 类 使JAVA 语 言 可 以 清 晰 地 表 达 程 序 员 的 意 图 而 不 会 增 加 混 乱。 因 为 它 们 是 在 编 译 器 中 实 现 的, 内 嵌 类 在 技 术 上 并 不 代 表 对JAVA(TM) 平 台 的 改 变, 但 它 们 肯 定 改 变 了JAVA 语 言。 当 内 嵌 类 与Core Reflection API 组 合 在 一 起 时, 它 们 能 使 我 们 建 立 动 态 链 接 的 回 调 机 制。AWT 中 新 的 事 件 模 块 是 这 种 机 制 的 一 个 很 好 的 例 子。

 

本文地址:http://www.45fan.com/dnjc/71173.html
Tags: Java 编译 孙国泉
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部