对 象 的 生 存 期 北 京 理 工 大 学 计 算 机 学 院 金 旭 亮
概 述 万 物 有 生 必 有 死, 类 似 地, 面 向 对 象 软 件 系 统 中 的 对 象, 既 然 是 对 现 实 世 界 的 一 种 模 拟, 必 然 也 存 在 着 生 死 问 题 对 象 的 生 与 死, 在 面 向 对 象 的 理 论 中, 称 为 对 象 的 生 存 期 问 题 2014/10/19 2 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程
一 对 象 的 创 建
对 象 的 创 建 与 销 毁 所 有 的 面 向 对 象 编 程 语 言, 都 必 然 会 提 供 特 定 的 关 键 字 或 手 段 创 建 一 个 对 象 以 C++/Java/C# 为 例, 都 是 使 用 new 关 键 字 创 建 对 象 的 对 象 创 建 以 后, 通 常 会 使 用 某 个 变 量 保 存 它 的 引 用, 这 个 变 量 可 视 为 它 所 引 用 对 象 的 名 字, 在 后 面 的 代 码 中 通 过 这 个 名 字 访 问 对 象 2014/10/19 4 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程
对 象 的 创 建 构 造 函 数 Demo(Constructor): 类 的 构 造 函 数 public class MyClass public MyClass() public MyClass(string info) 一 个 类 可 以 有 多 个 构 造 函 数, 这 些 构 造 函 数 构 成 重 载 (overload) 关 系 在 程 序 实 际 运 行 时, 依 据 参 数 决 定 调 用 哪 个 构 造 函 数 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 4
对 象 注 入 对 象 之 间 的 相 互 协 作 关 系 是 非 常 常 见 的, 比 如 类 A 要 用 到 类 B 的 功 能 在 实 际 开 发 中, 我 们 经 常 使 用 一 种 对 象 注 入 (Object Inject) 的 对 象 构 造 方 法 达 到 目 的 对 象 注 入 定 义 一 个 类 B 类 A 需 要 使 用 类 B, 它 在 内 部 用 一 个 私 有 字 段 保 存 外 部 注 入 的 B 对 象 的 引 用 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 6
实 际 开 发 中 推 荐 的 对 象 注 入 方 式 将 类 A 要 使 用 的 功 能 抽 象 为 接 口, 类 B 实 现 这 一 接 口, 然 后 在 运 行 时 动 态 地 将 类 B 的 实 例 注 入 // 定 义 一 个 接 口 interface MyInterface (1) (2) // 类 B 实 现 这 一 接 口 class B:MyInterface // 在 创 建 A 时, 动 态 注 入 一 个 B 对 象 A obj = new A(new B()); // 类 A 通 过 接 口 调 用 特 定 功 能 class A private MyInterface innerobj; public A(MyInterface obj) innerobj = obj; (3) (4) 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 7
对 象 注 入 的 真 实 案 例 JDK 中 的 流 (Stream) 普 通 采 用 对 象 注 入 的 方 式, 请 看 以 下 Java 示 例 代 码 FileInputStream fin=new FileInputStream( 文 件 名 ); DataInputStream din=new DataInputSteam(fin); Double s=din.readdouble(); 说 明 : 1. 上 述 这 种 代 码 创 建 的 流 对 象 首 尾 相 接, 称 为 流 对 象 链, 链 尾 的 流 对 象 是 程 序 中 真 正 用 到 的 流 对 象 2. FileInputStream 只 提 供 基 本 的 以 字 节 为 单 位 的 读 写 功 能, 而 DataInputStream 提 供 了 向 流 中 写 入 常 用 数 据 类 型 ( 如 int,char,double 等 ) 的 能 力 3. 这 实 际 上 是 一 种 设 计 模 式, 称 为 Decorator 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 8
再 来 看 另 一 个 场 景 在 实 际 开 发 中, 我 们 有 时 希 望 某 个 类 仅 允 许 创 建 一 个 实 例 ( 比 如 用 于 保 存 系 统 配 置 参 数 的 对 象 整 个 程 序 就 只 需 一 个, 在 程 序 初 始 时 创 建 它, 以 后 就 是 只 读 的 ), 怎 样 做 到 这 点? 一 个 最 简 单 的 方 法, 就 是 : 给 类 源 码 或 程 序 文 档 附 加 一 条 注 释, 告 诉 使 用 这 个 类 的 程 序 员, 在 整 个 程 序 生 命 周 期 中, 只 能 调 用 new 一 次 以 创 建 唯 一 的 对 象 很 明 显, 这 种 方 法 是 不 靠 谱 的 有 没 有 更 好 的 方 法? 2014/10/19 9 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程
解 决 问 题 的 技 术 关 键 点 当 一 个 类 的 构 造 方 法 是 私 有 的 时, 外 界 将 无 法 使 用 new 关 键 字 创 建 它 的 实 例 : 使 用 new 无 法 创 建 MyClass 对 象 那 么, 外 界 如 何 获 取 这 个 唯 一 的 对 象 呢? 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 10
使 用 静 态 成 员 解 决 前 面 提 出 的 问 题 我 们 可 以 设 置 一 个 私 有 静 态 字 段, 引 用 唯 一 的 那 个 对 象, 再 设 计 一 个 公 有 静 态 方 法, 强 制 外 界 只 能 通 过 调 用 此 静 态 方 法 才 能 获 取 这 个 唯 一 的 内 部 对 象 的 引 用 OnlyYou -obj : OnlyYou = null -OnlyYou() +GetOnlyYou() : OnlyYou 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 11
上 面 的 代 码 中 似 乎 还 有 问 题 请 考 虑 多 线 程 的 运 行 环 境 线 程 一 线 程 二 线 程 一 首 先 得 到 机 会 运 行, 它 检 查 obj, 发 现 其 为 null, 于 是 执 行 new 创 建 对 象 但 是, 此 对 象 的 构 造 方 法 还 未 返 回 时, 线 程 二 得 到 机 会 开 始 执 行, 它 检 查 obj, 此 时 obj 仍 然 还 是 null 结 果 是 什 么? 好 像 情 况 不 太 妙 啊! 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 12
多 线 程 问 题 解 决 方 案 -1 加 锁 解 决 问 题 使 用 CLR 支 持 的 同 步 方 法 特 性 这 两 种 方 案 都 具 有 对 象 延 迟 创 建 的 特 性, 这 就 是 说 : 只 有 在 需 要 时 才 创 建 对 象 如 果 允 许 提 前 创 建 对 象, 那 我 们 还 有 更 为 稳 妥 的 实 现 方 案 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 13
多 线 程 问 题 解 决 方 案 -2 使 用 类 的 静 态 构 造 函 数, 在 整 个 程 序 的 生 命 周 期 中, 类 的 静 态 构 造 函 数 仅 会 被 调 用 一 次.NET 虚 拟 机 (CLR) 保 证 这 个 方 法 仅 会 被 执 行 一 次 其 执 行 时 机 是 此 类 中 的 任 何 一 个 成 员 被 第 一 次 访 问 时 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 14
二 对 象 的 销 毁
对 象 的 销 毁 当 需 要 销 毁 对 象 时,C++ 提 供 了 一 个 delete 关 键 字 显 式 地 销 毁 对 象 并 回 收 其 占 用 的 内 存 Java/C# 与 C++ 不 一 样, 它 采 用 了 一 种 隐 式 回 收 的 方 式 销 毁 对 象 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 16
Java/C# 中 的 垃 圾 收 集 机 制 在 Java/C# 程 序 中, 你 new 了 一 个 对 象 之 后, 就 可 以 不 管 它 了 JVM/CLR 都 有 一 个 特 殊 的 机 制 垃 圾 收 集 (Garbage Collection) 来 负 责 对 象 的 回 收 当 JVM/CLR 发 现 内 存 紧 张 时, 会 启 动 一 个 线 程 完 成 GC 工 作,GC 线 程 会 扫 描 当 前 时 刻 应 用 程 序 创 建 的 所 有 对 象, 找 出 那 些 超 出 作 用 域 的 己 不 再 使 用 的 对 象, 销 毁 它 们, 回 收 其 占 用 的 资 源 2014/10/19 17 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程
主 动 探 索 JVM/CLR 所 提 供 的 对 象 自 动 回 收 机 制, 大 大 地 减 轻 了 程 序 员 的 负 担, 有 助 于 提 升 开 发 效 率 和 代 码 质 量 ( 对 C++ 程 序 的 统 计 表 明, 大 多 数 BUG 都 与 指 针 误 用 和 对 象 销 毁 相 关 ) 毫 无 疑 问, 在 虚 拟 机 中 引 入 GC 机 制, 必 然 会 占 用 部 分 的 系 统 资 源 为 了 将 这 种 影 响 限 制 于 一 个 比 较 合 理 的 范 围 之 内,Java 和.NET 虚 拟 机 都 精 心 设 计 了 垃 圾 收 集 算 法 请 感 兴 趣 的 同 学 通 过 互 联 网 搜 索 相 关 资 料, 看 看 这 些 算 法 是 怎 样 设 计 出 来 的 2014/10/19 18 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程
C#/Java 中 的 析 构 方 法 C++ 可 以 为 类 定 义 一 个 析 构 方 法, 当 对 象 被 销 毁 时, 此 析 构 方 法 被 调 用 Java/C# 中 存 在 类 似 的 特 性 C# Java C# 类 的 析 构 方 法, 实 际 上 是 重 写 了 基 类 的 Finalize() 方 法, 编 译 器 在 编 译 上 述 代 码 的 时 会 生 成 一 条 调 用 此 Finalize() 方 法 的 指 令 Java 允 许 子 类 重 写 Object.finalize() 方 法, 在 Object.finalize() 方 法 源 代 码 的 注 释 中, 详 细 介 绍 了 相 关 的 技 术 细 节 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 19
动 手 动 脑 class MyClass ~MyClass() // 析 构 函 数 // 编 写 代 码 释 放 非 托 管 的 资 源 使 用.NET 所 提 供 的 ildasm 工 具 反 汇 编 左 边 的 这 个 类 编 译 出 来 的 程 序 集 (exe 或 dll), 可 以 看 到 以 下 类 似 的 结 果, 请 动 手 一 试 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 20
关 于 C# 与 Java 的 析 构 方 法 在 实 际 开 发 中, 不 建 议 重 写 类 的 析 构 方 法, 其 主 要 原 因 在 于 这 个 方 法 的 调 用 时 机 是 由 虚 拟 机 的 GC 机 制 决 定 的, 应 用 程 序 开 发 者 无 法 确 认 这 个 方 法 一 定 在 特 定 的 时 机 被 调 用, 甚 至 不 敢 保 证 一 定 会 被 调 用 析 构 方 法 的 主 要 作 用 是 回 收 资 源, 但 为 了 实 现 这 一 目 的, 不 用 析 构 方 法 也 行, 只 需 定 义 另 一 个 诸 如 Dispose/Close() 之 类 的 方 法 显 式 回 收 资 源 即 可 下 面 的 PPT 中, 将 介 绍 C# 中 用 于 回 收 资 源 的 Disposal 设 计 模 式 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 21
关 于 析 构 方 法 析 构 函 数 存 在 的 主 要 目 的 是 释 放 非 托 管 的 资 源 当 CLR 的 垃 圾 收 集 线 程 要 回 收 一 个 定 义 了 析 构 方 法 的 对 象 时, 它 会 自 动 调 用 其 Finalize 方 法 假 设 子 类 定 义 了 析 构 方 法, 但 基 类 没 有 定 义, C# 编 译 器 所 采 取 的 策 略 它 会 跳 过 这 个 基 类 去 检 查 更 上 一 层 的 基 类 是 否 定 义 了 析 构 函 数, 如 果 有 则 调 用 之, 否 则 直 接 调 用 Object 类 的 Finalize 方 法 除 了 析 构 方 法,.NET 中 我 们 还 有 另 一 个 与 资 源 回 收 密 切 相 关 的 东 西 IDisposable 接 口 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 22
使 用 IDisposable 接 口 public interface IDisposable void Dispose(); class MyClass : IDisposable ~MyClass() Dispose(); public void Dispose() // 编 写 代 码 释 放 所 占 用 的 资 源 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 23
揭 秘 C# 中 的 using 关 键 字 using (MyClass obj = new MyClass()) // 使 用 obj 干 活 C# 编 译 器 在 编 译 包 容 using 关 键 字 的 代 码 时, 其 实 是 按 照 以 下 代 码 模 板 进 行 编 译 的 使 用 using 关 联 字 时, 它 所 关 联 的 对 象 必 须 实 现 IDisposable 接 口 MyClass obj = new MyClass(); try // 使 用 obj 干 活 finally IDisposable disposable = obj as IDisposable; if (obj!= null) obj.dispose(); // 释 放 资 源 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 24
析 构 方 法 与 Dispose() 方 法 类 的 析 构 方 法 通 常 用 于 清 理 非 托 管 的 资 源, 而 IDisposable 接 口 所 定 义 的 Dispose() 方 法, 则 可 以 同 时 清 理 托 管 和 非 托 管 的 资 源 只 有 类 需 要 提 供 一 个 在 应 用 程 序 中 可 随 时 调 用 的 显 式 清 理 所 占 用 资 源 的 功 能 时, 才 需 要 实 现 IDisposable 接 口 在 实 际 开 发 中, 应 注 意 不 管 资 源 是 否 己 经 被 释 放, 实 现 的 Dispose() 方 法 都 应 该 能 被 安 全 地 调 用 多 次, 不 要 让 使 用 这 个 类 的 程 序 员 去 操 心 到 底 调 用 了 多 少 次 Dispose() 方 法, 反 复 释 放 资 源 会 不 会 弄 出 什 么 问 题 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 25
IDisposable 编 程 模 式 代 码 框 架 class MyClass : IDisposable ~MyClass() Dispose(false); public void Dispose() Dispose(true); // 将 导 致 所 有 资 源 被 释 放 // 不 需 要 再 调 用 本 对 象 的 Finalize() 方 法 了 GC.SuppressFinalize(this); protected virtual void Dispose(bool disposing) if (disposing) // 清 理 托 管 的 资 源 // 清 理 非 托 管 资 源 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 26
真 实 案 例 分 析 FileStream private SafeFileHandle _handle; ~FileStream() if (this._handle!= null) this.dispose(false);.net 基 类 库 中 FileStream 类 既 重 写 了 析 构 方 法, 又 实 现 了 IDisposable 接 口, 这 里 展 示 了 它 的 关 键 代 码 注 意 FileStream 需 要 调 用 基 类 的 Dispose() 方 法 protected override void Dispose(bool disposing) try if ( this._handle!= null &&!this._handle.isclosed && ) this.flushwrite(!disposing); finally if ((this._handle!= null) &&!this._handle.isclosed) this._handle.dispose(); // base.dispose(disposing); 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 27
真 实 案 例 分 析 Stream public void Dispose() this.close(); public virtual void Close() this.dispose(true); GC.SuppressFinalize(this); FileStream 派 生 自 Stream, 这 里 是 其 基 类 Stream 中 与 资 源 回 收 相 关 的 关 键 代 码 注 意 与 前 页 PPT 结 合 起 来 看, 因 为 其 子 类 FileStream 调 用 了 基 类 Stream 的 相 关 方 法 protected virtual void Dispose(bool disposing) if (disposing && (this._asyncactiveevent!= null)) this._closeasyncactiveevent( ); 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 28
学 以 致 用 请 用 C# 或 Java 编 写 一 个 GUI 程 序, 其 窗 体 如 下 所 示 : 功 能 描 述 : 每 次 点 击 第 一 个 按 钮, 都 会 在 屏 幕 上 出 现 一 个 新 窗 体 当 创 建 了 多 个 新 窗 体 之 后, 点 击 第 二 个 按 钮, 所 有 这 些 窗 体 都 会 被 关 闭 思 索 以 下 问 题 : 如 果 用 户 关 闭 了 其 中 的 某 个 或 某 几 个 窗 体, 程 序 应 该 如 何 处 理 这 种 情 况? 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 29
小 结 对 象 的 生 存 期 是 面 向 对 象 软 件 开 发 中 的 一 个 重 要 问 题, 本 讲 围 绕 着 这 点 而 展 开, 其 重 点 内 容 如 下 : 1. 对 象 注 入 的 编 程 方 法 2. 静 态 成 员 的 多 线 程 安 全 3. IDisposable 编 程 模 式 有 关 对 象 生 存 期 问 题, 在 后 面 介 绍 各 种 面 向 对 象 技 术 框 架 时 还 会 涉 及 到 开 发 中 务 必 注 意 它 下 一 讲, 我 们 将 介 绍 面 向 对 象 编 程 中 的 另 一 个 重 要 知 识 点 多 态 2014/10/19 金 旭 亮 面 向 对 象 软 件 开 发 实 践 课 程 30