如何在Java程序中使用运行时间类信息
---- Java 对 象 模 型 最 酷 的 是, 它 是" 有 意 识" 的: 给 出 一 个 对 象 的 引 用, 你 就 可 以 获 得 关 于 该 对 象 类 的 信 息。 这 一" 运 行 时 间 类 信 息" 使 所 有 类 型 的 有 趣 设 计 与 技 术 实 现 成 为 可 能。 但 是, 应 如 何 使 用 呢 ?
---- Java 对 运 行 时 间 类 信 息 的 支 持, 使 人 们 很 容 易 想 到 两 点:
信 息 就 是 权 力。 运 行 时 间 类 信 息 赋 予 了 程 序 员 更 多 的 权 力。 这 是 好 事。
权 力 导 致 腐 化。 也 就 是 说, 运 行 时 间 类 信 息 可 能 在 设 计 中 被 滥 用。
---- 本 文 正 是 试 图 回 答 如 下 问 题: 如 何 合 理 利 用 运 行 时 间 类 信 息。 运 行 时 间 类 信 息 是 如 何 起 作 用 的
---- Java 编 程 中 每 一 个 类(class) 或 界 面(interface) 都 为 程 序 定 义 了 新 的 类 型(type)。 当 定 义 了 一 个 新 类 型 时, 就 可 以 声 明 该 类 型 的 变 量。 一 个 类 型 定 义 了 一 组 操 作 以 及 这 些 操 作 的 含 义。
---- 比 方 说, 你 定 义 了 一 个 类Cat, 你 就 可 以 定 义 类 型Car 的 变 量。 对 于 这 些 变 量,Java 虚 拟 机(JVM) 就 会 允 许 执 行 你 为Cat 类 型 定 义 的 操 作, 但 不 会 允 许 执 行 其 它 操 作。 如 果Car 类 声 明 了 一 个 公 共 方 法takeBath(),JVM 就 会 允 许takeBath() 调 用Cat 类 型 的 变 量。
---- 当 对 类 或 界 面 进 行 编 译 时,Java 编 译 器 会 给 出 一 个 类 文 件。 该 类 文 件 是 一 个 中 间 编 译 的 二 进 制 格 式 的 文 件, 用 来 描 述 一 个Java 类 型( 一 个 类 或 界 面)。Cat 的 该 类 文 件 可 能 包 括 所 有 定 义 该 类 需 要 的 信 息, 如:
---- 类 型 的 名 称(Cat);
---- 父 类 的 名 字;
---- 任 何 修 饰 符( 如public 或abstract);
---- 该 类 型 直 接 实 现 的 界 面 数;
---- 这 些 界 面 的 名 字;
---- 场(field) 的 数 目;
---- 每 个 场 的 描 述 符( 场 名、 类 型、 修 饰 符);
---- 方 法 的 数 目;
---- 每 个 方 法 的 描 述 符( 方 法 名、 返 回 类 型 或void、 参 数 数 目 与 类 型)、 修 饰 符(static、abstract、private 等)、 字 节 码 和 一 个 例 外 表。
---- 每 当JVM 装 载 一 个 类 型( 一 个 类 或 界 面) 时,JVM 会 将 该 类 型 的 信 息 保 存 在 其 内 存 的 一 个 逻 辑 区 内( 称 为 方 法 区method area)。 储 存 在 方 法 区 内 的 信 息 与 类 文 件 中 的 信 息 是 对 应 的, 当 与 类 文 件 不 同 的 是, 其 结 构 与 组 织 不 是 由Java 规 范 定 义 的。 每 个JVM 的 设 计 者 决 定 其 储 存 的 具 体 方 式。 对 于 它 装 载 的 每 个 类 型,JVM 创 建 一 个java.lang.Class 类 的 实 例, 作 为 该 应 用 中 该 类 型 的 代 表。Java 中 数 组 也 是 类 对 象( 相 同 类 型 和 维 数 的 数 组 共 享 共 一 个 类 对 象)。 一 个 类 对 象 赋 予 你 访 问 存 储 在 方 法 区 中, 类 对 象 代 表 的 类 型 的 信 息。
---- 尽 管Java 规 范 没 有 为 堆 中 的 对 象 定 义 一 个 方 案 或 组 织 - 这 是 每 个JVM 设 计 者 的 决 定, 但 是 该 规 范 要 求 对 象 映 像 以 某 种 方 式 与 对 象 类 的 类 型 数 据 进 行 连 接。JVM 设 计 者 使 用 的 一 种 方 法 是 在 方 法 区 中 包 括 一 个 指 针, 并 将 其 作 为 每 个 对 象 映 像 的 一 部 分。 只 给 出 一 个 对 象 的 引 用, 每 个JVM 都 必 须 能 获 得 储 存 在 方 法 区 中 的 该 对 象 的 类 型 信 息。
如 何 操 作 ---- 现 在 知 道 了 所 有 这 些 类 信 息 都 可 为 堆 中 的 每 个 对 象 所 利 用, 当 如 何 得 到 这 些 信 息 呢 ?Java 给 出 了 许 多 使 用 存 储 在 方 法 区 中 的 信 息 的 方 法:
---- 执 行 一 个cast( 抛 投)
---- 使 用instanceof 操 作 符
---- 调 用 一 个 实 例 方 法
---- 使 用java.lang.Class
---- Upcasts
---- 使 用 运 行 时 间 类 信 息 的 最 简 单 方 式 可 能 是 执 行 一 个cast。 作 为Java 的 一 个 安 全 机 制,JVM 在 运 行 时 刻 检 查 所 有cast 合 法 性。 如 果 你 有 一 个 类 型 对 象 的 引 用, 并 且 想 将 其 抛 到 类 型Cat,JVM 会 使 用 某 种 方 式 来 保 证 这 一 操 作 时 合 法 的。 该 检 查 使 用 运 行 时 间 类 信 息。 如 果 不 是 合 法 的,JVM 会 将 其 仍 到ClassCastException 例 外 中。Cast 按 其 使 用 方 式 的 不 同, 可 提 高 或 降 低 代 码 的 灵 活 性。 这 对 于 向 上 抛 投(upcast)- 从 子 类 型 向 父 类 型 抛 投, 是 很 有 用 的。 进 行 灵 活 的 面 向 对 象 设 计 的 一 个 基 本 方 针 是 尽 可 能 普 通 地 对 待 对 象, 使 你 的 变 量 的 类 型 尽 可 能 在 继 承 树 上 更 深 的 层 上。
---- 如 果 你 的 代 码 必 须 操 作Cat 对 象, 你 可 以 用 一 个Cat 对 象 的 变 量 来 引 用 该 对 象。 当 如 果Car 类 扩 展 了Animal 类, 那 么, 如 果 你 所 做 的 也 是 对 任 何 动 物 做 的, 就 应 将 你 的 引 用 抛 向Animal。 你 以 后 可 为Animal 的 其 它 子 类 使 用 同 样 的 代 码, 如Dog 或Fox, 而 不 只 是Car 对 象。
Downcasts 和 instanceof ---- 与upcasts 不 同,downcasts( 向 下 抛 投, 从 父 类 向 子 类 抛 投) 对 于 灵 活 性 来 说 可 能 更 麻 烦。 一 般 来 说, 作 为 显 性 向 下 抛 投 的 替 代, 可 用 动 态 捆 绑 来 实 现 多 态 性。Downcasts 经 常 与instanceof 操 作 符 一 起 使 用。intanceof 是 另 一 个 使 用 运 行 时 间 类 信 息 的Java 机 制。 当JVM 执 行 一 个instanceof 操 作 时, 它 参 考 与 对 象 引 用 相 连 的 运 行 时 间 类 信 息 来 确 定 该 对 象 是 否 是 特 定 类 的 一 个 实 例。 下 面 是 一 个 例 子, 它 展 示 了 向 下 抛 投 与instanceof 的 使 用 可 能 损 害 灵 活 性:
.
.
.
---- 尽 管 从 功 能 来 说, 上 例 是 正 确 的, 但 是 上 面 的 设 计 应 为 每 个 面 向 对 象 的 设 计 者 敲 响 警 钟。 在makeItTalk() 方 法 中instanceof 的 使 用 以 及 向 下 抛 投 是 一 个 在Java 程 序 中, 运 行 时 间 类 信 息 被 滥 用 的 典 型 例 子。
---- 这 一 方 式 带 来 的 麻 烦 是, 如 果 你 加 入 一 个 新 的Animal 子 类 型, 如Orangutan, 你 就 必 须 在makeItTalk() 方 法 中 加 入 一 个 新 的else-if 子 句。 多 态 性 和 动 态 捆 绑 会 自 动 为 你 考 虑 此 事。( 多 态 性 意 味 着, 你 可 以 用 一 个 父 类 变 量 来 引 用 该 父 类 或 任 一 子 类 的 对 象。)
---- 下 面 的 程 序 才 是 更 适 合 的:
.
.
.
---- 因 为Animal 类 声 明 了 一 个Dog、Cat 和Hipplpotamus 覆 盖 了 的talk() 方 法, 所 以makeItTalk() 自 需 用Animal 的 引 用 来 调 用talk() 即 可。 动 态 捆 绑 确 保 了talk() 的 正 确 版 本 被 调 用。 如 果Animal 传 到makeItTalk() 的 是Dog, 那 么,makeItTalk() 就 会 调 用Dog 对Talk() 的 实 现。 多 态 性 和 动 态 捆 绑 使 你 的 代 码 不 必 知 道 子 类 型。 当 你 调 用 一 个 实 例 方 法 时, 你 不 必 通 过instanceof 运 算 符 精 确 地 使 用 运 行 时 间 类 信 息,JVM 会 使 用 运 行 时 间 类 信 息 来 指 出 方 法 的 哪 个 实 现 会 被 执 行。
Downcast 与instanceof 的 合 理 使 用 ---- 尽 管 向 下 抛 投 和instanceof 可 能 被 滥 用, 当 它 们 同 样 有 合 理 的 用 法。 向 下 抛 投 的 一 个 普 通 用 法 是 将 从 一 个Collection 中 抽 出 的 一 个 对 象 引 用 抛 到 一 个 更 明 确 的 子 类 型 中。 下 面 是 一 个 例 子:
... ...
---- 本 例 中,Iterator 的next() 方 法 返 回 一 个Cat 对 象 的 引 用, 但 是 该 引 用 的 类 型 是 对 象。 该 引 用 立 即 向 下 抛 投 给Cat, 以 使yawn() 方 法 能 在 该 对 象 上 被 调 用。
---- 注 意, 此 处 并 不 需 要instanceof, 因 为makeCatYawn() 方 法 的 预 处 理 规 定, 由 参 数ca 传 送 的Collection 装 满 了Cat 对 象。 不 过, 这 一 代 码 使 用 了 运 行 时 间 类 信 息, 因 为JVM 检 查 了 该cast 是 否 合 法。 换 句 话 说,JVM 确 保 了 由next() 返 回 值 引 用 的 对 象 确 实 是Cat。 如 果 不 是,JVM 会 仍 出ClassCastException, 且makeCatYawn() 方 法 会 完 全 中 断。
instanceof ---- instanceof 的 基 本 用 法 是 指 出 你 是 否 能 针 对 一 个 对 象 执 行 某 些 类 型 的 操 作。 下 例 中,DancingCat 类(Cat 类 的 一 个 子 类) 声 明 了 一 个dance() 方 法。 类Example2 的makeCatDance() 方 法 假 设 它 接 收cat 的 一 个collection,e 而 不 必 是DancingCat 对 象。 它 使 用instanceof 指 出 在collection 中 的 每 个 对 象 是 否 是DancingCat 对 象 的 一 个 实 例。 对 于 它 找 到 的 每 个DancingCat 实 例, 它 都 向 下 抛 投 对DancingCat 的 该 引 用, 并 调 用dance()。
... ...
---- 上 例 突 出 了instanceof 基 本 的 合 理 用 法: 指 出 一 个 对 象 是 否 能 为 你 做 什 么。 当instanceof 返 回true, 该 引 用 向 下 抛 投 到 一 个 更 明 确 的 类 型 中, 以 使 方 法 能 调 用 它。
---- 一 个 好 的 一 般 原 则 是, 不 应 写 一 些 不 只 结 果 的 代 码, 但 是 有 时 又 是 不 可 避 免 的。 这 时,instanceof 是 首 选 的 方 法, 用 来 指 出 你 是 否 得 到 了 你 要 处 理 的 特 定 类 型。
类"Class" ---- java.lang.Class 类 的 实 例 描 绘 了JVM 中 运 行 时 间 类 信 息 的 最 丰 富 资 源。 一 旦 有 一 个 对 一 个 对 象 的 类 实 例 的 引 用, 就 可 以 访 问 该 对 象 的 类 的 大 量 信 息。
---- 你 可 以 通 过 调 用getClass(), 来 得 到 任 何 对 象 的 类 实 例 的 引 用:
static void printClassInfo(Object o) {
System.out.println("------------------------");
Class c = o.getClass();
// ...
---- 如 果 你 知 道 在 编 译 时 刻 的 类 型, 就 可 以 使 用 下 述 语 法 得 到 一 个 类 型 的 类 实 例 的 引 用:.class。 下 面 是 一 个 引 用 程 序, 它 访 问 并 打 印 相 关 信 息:
.
.
.
下 面 是 上 例 的 输 出 结 果:
.
.
.
总 结 -总结本文,有如下指导方针:
---- 试 着 将 你 的 变 量 的 类 型 保 持 在 继 承 树 中 的 高 位。 这 可 使 你 尽 可 能 一 般 的 对 待 对 象, 从 而 也 使 你 能 利 用 动 态 捆 绑 和 多 态 性 的 好 处。 当 引 用 的 类 型 是 该 引 用 对 象 实 际 所 属 类 的 父 类,JVM 就 会 使 用 动 态 捆 绑 来 为 用 该 引 用 调 用 的 方 法 实 现 作 出 正 确 的 定 位。