第一章
??????
5.13 动 态 成 员 函 数 发 送
当 你 用 点 操 作 符 调 用 一 个 对 象 实 例 的 成 员 函 数 时, 对 象 实 例 所 属 的 类 在 编 译 时 要 被 检 查, 以 确 保 调 用 的 成 员 函 数 在 该 类 中 是 存 在 的。 在 运 行 时, 对 象 实 例 可 以 指 向 所 声 明 类 型 的 子 类 的 实 例。 在 这 ?copy; 情 况 下, 如 果 子 类 覆 盖 了 要 调 用 的 成 员 函 数,Java就 用 实 例 来 决 定 调 用 哪 一 个 成 员 函 数。 如 下 面 的 例 子, 两 个 类 是 子 类 和 超 类 的 关 系, 子 类 覆 盖 了 超 类 的 成 员 函 数。
class A { void callme( ) { System.out.println(\"在A的callme成 员 函 数 里\"); } }
class B extends A { void callme( ) { System.out.println(\"在B的callme成 员 函 数 里\"); } }
class Dispatch { public static void main(String args[]) { A a = new B( ); a.callme( ); } }
有 趣 的 是, 在 成 员 函 数main里, 我 们 把 变 量a声 明 为 类 型A, 然 后 把 类B的 一 个 实 例 存 放 到 它 上 面。 我 们 在a上 调 用 成 员 函 数callme,Java编 译 器 确 定 在 类A确 实 有 成 员 函 数callme, 但 是 在 运 行 时, 由 于a事 实 上 是B的 实 例, 所 以 调 用B的callme, 而 不 调 用A的。 下 面 是 运 行 结 果: C:\\>java Dispatch 在B的callme成 员 函 数 里
5.14 final
在 缺 省 情 况 下, 所 有 的 成 员 函 数 和 实 例 变 量 都 可 以 被 覆 盖。 如 果 你 希 望 你 的 变 量 或 成 员 函 数 不 再 被 子 类 覆 盖, 可 以 把 它 们 声 明 为final。 这 意 味 着 将 来 的 实 例 都 依 赖 这 个 定 义。 例 如: final int FILE_NEW = 1; final int FILE_OPEN = 2; final int FILE_SAVE = 3; fianl int FILE_SAVEAS = 4; final int FILE_QUIT = 5; final变 量 用 大 写 标 识 符 是 一 个 一 般 的 约 定。
5.15 静 态
如 果 你 想 要 创 建 一 个 可 以 在 实 例 的 外 部 调 用 的 成 员 函 数, 那 么 你 只 需 声 明 它 为 静 态 的 (static?copy;, 它 就 会 正 常 运 行。 静 态 成 员 函 数 只 能 直 接 调 用 其 他 静 态 成 员 函 数, 而 不 能 以 任 何 方 式 使 用this或super。 你 也 可 以 把 变 量 声 明 为 静 态 的。 如 果 你 想 初 始 化 一 个 静 态 变 量, 你 可 以 用 static声 明 一 个 恰 好 在 类 调 用 时 执 行 一 次 的 程 序 块。 下 面 的 例 子 是 一 个 带 有 一 个 静 态 成 员 函 数, 几 个 静 态 变
量, 和 一 个 静 态 初 始 块 的 类。
class Static { static int a = 3; static int b; static void method(int x){ System.out.println(\"x = \" + x); System.out.println(\"a = \" + a); System.out.println(\"b = \" + b); } static { System.out.println(\"静 态 初 始 块\"); b = a * 4; } public static void main(String args[]) { method(42); } } 这 个 类 被 调 用, 所 有 的 静 态 变 量 都 被 初 始 化,a被 赋 为3, 然 后 运 行static块, 这 将 打 印 出 一 段 消 息, 并 且 把b赋 为a*4, 即12。 然 后 解 释 器 调 用main成 员 函 数, 它 调 用 了 成 员 函 数 method, 参 数x为42。 这 三 个println语 句 打 印 了 两 个 静 态 变 量a、b和 局 部 变 量x。 下 面 是 运 行 结 果: C:\\>java Static 静 态 初 始 块 x = 42 a = 3 b = 12 一 个 静 态 成 员 函 数 可 以 通 过 它 所 属 的 类 名 来 调 用。 象 调 用 实 例 变 量 一 样, 你 可 以 用 点 操 作 符 通 过 类 名 来 调 用 静 态 成 员 函 数 和 静 态 变 量。Java就 是 这 样 实 现 了 全 局 函 数 和 全 局 变 量。 下 面 的 例 子 里, 我 们 创 建 了 带 有 一 个 静 态 成 员 函 数 和 两 个 静 态 变 量 的 类。 第 二 个 类 可 以 通 过 名 字 直 接 来 调 用 第 一 个 类 的 静 态 成 员 函 数 和 静 态 变 量。
class staticClass { static int a = 42; static int b = 99; static void
callme( ) { System.out.println(\"a = \" + a); } }
class StaticByName { public static void main(String args[])
{ StaticClass.callme( ); System.out.println(\"b = \" + staticClass.b); } }
下 面 是 运 行 结 果: C:\\>java staticByName a = 42 b = 99
5.16 抽 象
有 时 你 需 要 定 义 一 个 给 出 抽 象 结 构、 但 不 给 出 每 个 成 员 函 数 的 完 整 实 现 的 类。 如 果 某 个 成 员 函 数 没 有 完 整 实 现, 必 须 要 由 子 类 来 覆 盖, 你 可 把 它 声 明 为 抽 象(abstract?copy; 型。 含 有 抽 象 型 成 员 函 数 的 类 必 须 声 明 为 抽 象 的。 为 了 把 一 个 类 声 明 为 抽 象 的, 你 只 需 在 类 定 义 的class关 键 词 前 放 置 关 键 词abstract。 这 ?copy; 类 不 能 直 接 用new操 作 符 生 成 实 例, 因 为 它 们 的 完 整 实 现 还 没 有 定 义。 你 不 能 定 义 抽 象 的 构 造 函 数 或 抽 象 的 静 态 成 员 函 数。 抽 象 类 的 子 类 或 者 实 现 了 它 的 超 类 的 所 有 抽 象 的 成 员 函 数, 或 者 也 被 声 明 为 抽 象 的。 下 面 例 子 是 一 个 带 有 抽 象 成 员 函 数 的 类, 其 后 是 一 个 实 现 了 该 成 员 函 数 的 类。
abstract class A { abstract void callme( ) ; void metoo( ) { system.out.println(\"在A的metoo成 员 函 数 里\"); } } class B extends A { void callme( ) { System.out.println(\"在B的callme成 员 函 数 里\"); } } class Abstract { public static void main(String args[]) { A a = new B( );
a.callme( ); a.metoo( ); } }
下 面 是 运 行 结 果: C:\\>java Abstract 在B的callme成 员 函 数 里 在A的metoo成 员 函 数 里
本 章 小 结
1. 类 是Java语 言 面 向 对 象 编 程 的 基 本 元 素, 它 定 义 了 一 个 对 象 的 结 构 和 功 能。 2. Java通 过 在 类 定 义 的 大 括 号 里 声 明 变 量 来 把 数 据 封 装 在 一 个 类 里, 这 里 的 变 量 称 为 实 例 变 量。 3. 成 员 函 数, 是 类 的 功 能 接 口, 是 类 定 义 里 的 一 个 子 程 序, 在 类 的 定 义 里 和 实 例 变 量 处 于 同 一 级 别。
第 六 章 Java图 形 用 户 接 口
对 一 个 优 秀 的 应 用 程 序 来 说, 良 好 的 图 形 用 户 接 口 是 必 不 可 少 的。 缺 少 良 好 的 图 形 用 户 接 口, 将 会 给 用 户 理 解 和 使 用 应 用 程 序 带 来 很 多 不 便。 很 难 想 象 用 户 为 了 学 会 使 用 一 个 应 用 程 序, 去 记 一 大 堆 命 令。 Java提copy; 了 生 成 一 个 良 好 的 图 形 用 户 接 口 所 需 要 的 一copy; 基 本 元 件: 面 板(Panel copy;、 按 钮 (Button copy;、 标copy;(Label copy;、 画 板(Canvases copy;、 滚 动 条(Scrollbar copy;、 列 表 框(List copy;、 文 本 域(Text Field copy;、 文 本 区(Text Area copy;。
6.1 面 板
面 板 提copy; 了 建 立 应 用 程 序 的 空 间。 你 可 以 把 图 形 元 件(包 括 其 他 面 板 copy; 放 在 一 个 面 板 上。 Applet类 提copy; 了 一 个 基 本 的 面 板。
6.1.1 布 局 管 理
Java提copy; 了 几 种 布 局: 顺 序 布 局(Flow Layout copy;、 边 界 布 局(Border Layout copy; 和 网 格 布 局 (Grid Layout)
6.1.1.1 顺 序 布 局
顺 序 布 局(Flow Layout copy; 是 最 基 本 的 一 种 布 局, 面 板 的 缺 省 布 局 就 是 顺 序 布 局。 顺 序 布 局 指 的 是 把 图 形 元 件 一 个 接 一 个 地reg; 平 地 放 在 面 板 上。 下 面 是 一 个 顺 序 布 局 的 例 子:
import java.awt.*; import java.applet.Applet;
public class myButtons extends Applet { Button button1, button2, button3;
public void init() { button1 = new Button(\"确 定\"); button2 = new Button(\"打 开\"); button3 = new Button(\"关 闭\"); add(button1); add(button2); add(button3); } }
6.1.1.2 边 界 布 局
边 界 布 局 包 括 五 个 区: 北 区、 南 区、 东 区、 西 区 和 中 区。 这 几 个 区 在 面 板 上 的 分 布 规 律 是\" 上 北 下 南, 左 西 右 东\"。 下 面 是 一 个 边 界 布 局 的 例 子:
import java.awt.*; import java.applet.Applet;
public class buttonDir extends Applet { Button buttonN, buttonS, buttonW, buttonE, buttonC;
public void init() { setLayout(new BorderLayout()); buttonN = new Button(\" reg;\"); buttonS = new Button(\"火\"); buttonE = new Button(\"木\"); buttonW = new Button(\"金\"); buttonC = new Button(\"土\"); add(\"North\", buttonN); add(\"South\", buttonS); add(\"East\", buttonE); add(\"West\", buttonW); add(\"Center\", buttonC); } }
6.1.1.3 网 格 布 局
网 格 布 局 把 面 板 分 成 一 个 个 的 网 格, 你 可 以 给 出 网 格 的 行 数 和 列 数。 下 面 是 一 个 网 格 布 局 的 例 子:
import java.awt.*; import java.applet.Applet;
public class buttonGrid extends Applet { Button button1, button2, button3, button4, button5, button6, button7, button8;
public void init() { setLayout(new GridLayout(4,2)); button1 = new Button(\"乾\"); button2 = new Button(\"坤\"); button3 = new Button(\"艮\");
button4 = new Button(\"震\"); button5 = new Button(\"坎\"); button6 = new Button(\"离\"); button7 = new Button(\"巽\"); button8 = new Button(\"兑\");
add(button1); add(button2); add(button3); add(button4); add(button5); add(button6); add(button7); add(button8); } }
6.2 按 钮
6.2.1 按 钮 事 件
用 户 点 一 下 按 钮, 就 会 有 一 个 按 钮 事 件 发 生。 你 可 以 通 过 覆 盖 一 个applet的action成 员 函 数 来 捕 捉 按 钮 事 件。 public boolean action (Event e, Object o) { if (e.target instanceof Button) { system.out.println ((string) o); } else { System.out.println (\"Non-button event\"); } return true; }
6.2.2 按 钮 类 型
Java提copy; 了 标 准 的 按 压 式 按 钮, 同 时 也 提copy; 了 选 择 式 按 钮 和 标 记 式 按 钮。
6.2.2.1 选 择 式 按 钮
选 择 式 按 钮 提copy; 了 从 几 个 选 项 中 选 一 个 选 项 的 功 能。 下 面 是 从 几 个 市 中 选 一 个 市 的 例 子, 市 名 放 在 选 择 式 按 钮 中:
CityChooser = new Choice();
CityChooser.addItem(\"北copy;\"); CityChooser.addItem(\"上海\");
CityChooser.addItem(\"天 津\");
add(CityChooser);
6.2.2.2 标 记 式 按 钮
标 记 式 按 钮 的 状 态 作 为 标 记 框 事 件 的 对 象 参 数 返 回。 下 面 是 一 个 标 记 式 按 钮 的 例 子:
Checkbox fillStyleButton; fillStyleButton = new Checkbox(\"Solid\");
public boolean action(Event e, Object arg) { if (e.target instanceof
Checkbox) { System.out.println(\"Checkbox: \" + arg); } return true; }
6.2.2.3 按 键 式 按 钮
按 键 式 按 钮 是 一 组 按 钮, 用 户 可 以 选 中 其 中 一 个, 同 时 这 一 组 中 的 其 他 按 钮 将 被 关 闭。 下 面 是 一 个 按 键 式 按 钮 的 例 子: public class CheckBox extends Applet { CheckboxGroup cbg; public void init() { cbg = new CheckboxGroup(); add (new Checkbox(\"one \", cbg, true)); add (new Checkbox(\"two \", cbg,false)); add (new Checkbox(\"three\", cbg, false)); } }
6.2.3 自 包 含 按 钮
Java语 言 的 面 向 对 象 特 性 使 我 们 能 够 创 建 完 全 自 包 含 的 按 钮。 在 自 包 含 按 钮 里, 你 可 以 在copy; 展 按 钮 类 里 建 立 事 件 控 制 函 数。 下 面 是 一 个 自 包 含 按 钮 的 例 子:
import java.awt.*; import java.applet.Applet;
class okButton extends Button {
public okButton() { setLabel(\"Ok\"); }
public boolean action(Event e, Object arg)
{ System.out.println(\"OKButton\"); return true; } }
public class buttontest extends Applet { okButton myOkButton;
public void init() { myOkButton = new okButton(); add(myOkButton); } }
6.3 标copy;
标copy; 是 一 种 放 到 面 板 上 的 静 止 的 正 文。 下 面 是 一 个 标copy; 的 例 子: import java.awt.*; import java.applet.Applet; public class label extends Applet { public void init() { setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); Label label1 = new Label(\"你 好!\"); Label label2 = new Label(\"另 一 个 标copy;\"); add(label1); add(label2); } }
6.4 列 表 框
列 表 框 使 用 户 易 于 操 作 大 量 的 选 项。 创 建 列 表 框 的 方 法 和Choice button有copy; 相 似。 列 表 框 的 所 有 条 目 都 是 可 见 的, 如 果 选 项 很 多, 超 出 了 列 表 框 可 见 区 的 范 围, 则 列 表 框 的 旁 边 将 会 有 一 个 滚 动 条。 首 先, 创 建 列 表 框: List l = new List(4, false); 这 个 成 员 函 数 创 建 了 一 个 显 示4行 的 列 表 框。 第 二 个 参 数\"false\"表 示 这 个 列 表 框 是 单 选 的, 如 果 是\"true \", 则 表 示 是 多 选 的。 下 面 增 加 列 表 框 的 选 项: l.addItem(\"北copy; 大 学\"); l.addItem(\"清 华 大 学\"); l.addItem(\"吉林 大 学\"); l.addItem(\"复copy; 大 学\"); l.addItem(\"南 开 大 学\"); l.addItem(\"天 津 大 学\"); l.addItem(\"南copy; 大 学\"); add(l);
6.4.1 在 列 表 框 中 进 行 选 择
可 以 用 成 员 函 数getSelectedItem()或getSelectedItems()来 接 收 在 列 表 框 中 被 选 的 选 项。 在 单 选 列 表 框 里,\" 双 击\" 一 个 选 项 就 可 以 触 发 一 个 可 被action()成 员 函 数 捕 捉 到 的 事 件。 public boolean action(Event e, Object arg) { . . . if (e.target instanceof List) { System.out.println(\"List entry:\" + arg); } . . . }
6.4.2 多 选 列 表 框
对 于 多 选 列 表 框, 要 使 你 的 选 择 产 生 作 用, 需 要 使 用 其 他 的 外 部 事 件。 例 如, 你 可 以 使 用 按 钮 事 件: public boolean action(Event e, Object arg) { . . . if (e.target instanceof Button) { . . . if (\"Ok\".equals(arg)) { string[] selected ; selected = l.getSelectedItems( ); for (int I = 0; I< selected.length; I++) {System.out.println(selected[i]); } } } }
6.5 文 本 域
文 本 域 一 般 用 来 让 用 户 输 入 象 姓 名、 信 用 卡 号 这 样 的 信 息, 它 是 一 个 能 够 接 收 用 户 的 键 盘 输 入 的 小 块 区 域。
6.5.1 创 建 文 本 域
在 创 建 文 本 域 时, 有 四 种 类 型copy; 你 选 择: 空 的、 空 的 并 且 具 有 指 定 长 度、 带 有 初 始 文 本 内 容 的 和 带 有 初 始 文 本 内 容 并 具 有 指 定 长 度 的。 下 面 是 生 成 这 四 种 文 本 域 的 代 码: TextField tf1, tf2, tf3, tf4; // 空 的 文 本 域 tf1 = new TextField() ; // 长 度 为20的 空 的 文 本 域 tf2 = new TextField(20) ; // 带 有 初 始 文 本 内 容 的 文 本 域 tf3
= new TextField(\"你 好\") ; // 带 有 初 始 文 本 内 容 并 具 有 指 定 长 度 的 文 本 域 tf4 = new TextField(\"你 好\", 30) ; add(tf1) ; add(tf2) ; add(tf3) ; add(tf4) ;
6.5.2 文 本 域 事 件
当 用 户 在 文 本 域 里 敲\" 回 车\" 键 时, 就 产 生 了 一 个 文 本 域 事 件。 象 其 他 事 件 一 样, 你 可 以 以 在 成 员 函 数action()中 捕 捉 到 这 个 事 件。
public boolean action(Event e, Object arg) { . . . if (e.target instanceof TextField) { System.out.println(\"TextField: \"+arg); } . . . }
6.6 文 本 区
文 本 区 可 以 显 示 大 段 的 文 本。
6.6.1 创 建 文 本 区
与 文 本 域 类 似, 创 建 文 本 区 时 也 有 四 种 类 型copy; 选 择, 但 如 果 指 定 文 本 区 的 大 小, 必 须 同 时 指 定 行 数 和 列 数。 TextArea ta1, ta2; // 一 个 空 的 文 本 区 ta1 = new TextArea(); // 一 个 带 有 初 始 内 容、 大 小 为5 x 40 的 文 本 区 ta2 = new TextArea(\"你 好!\", 5, 40);
可 以 用 成 员 函 数setEditable()来 决 定 用 户 是 否 可 对 文 本 区 的 内 容 进 行 编 辑。 // 使 文 本 区 为 只 读 的 ta2.setEditable(false)
6.6.2 接 收 文 本 区 的 内 容
可 以 用 成 员 函 数getText()来 获 得 文 本 区 的 当 前 内 容。 例 如: System.out.println(ta1.getText()); 文 本 区 本 身 不 产 生 自 己 的 事 件。 但 你 可 以 用 外 部 事 件 来 接 收 文 本 区 的 内 容: public boolean action(Event e, Object o) { if (e.target instanceof Button) { if (\"send\".equals(o)) { String textToSend = ta1.getText ();
System.out.println(\"sending: \" + textTosend);
mySendFunction(textToSend); } } else { . . . } }
6.7 画 板
画 板 能 够 捕 捉 到copy; 露 事 件、 鼠 标 事 件 和 其 他 类 似 的 事 件。 基 本 的 画 板 类 不 处 理 这copy; 事 件, 但 你 可 以copy; 展 它 来 创 建 有 你 所 需 功 能 的 画 板 类。
6.7.1 创 建 画 板
import java.awt.*; import java.applet.Applet;
public class superGUI extends Applet { . . . myCanvas doodle; . . . public
void init() { . . . // 建 立 我 们 的 画 板 doodle = new myCanvas();
doodle.reshape(0, 0, 100, 100); leftPanel.add(\"Center\",doodle); . . . } }
class myCanvas extends Canvas { public void paint(Graphics g)
{ g.drawRect(0, 0, 99, 99); g.drawString(\"Canvas\", 15, 40); } }
6.7.2 画 板 事 件
你 可 以 覆 盖 一 般 的 事 件 处 理 成 员 函 数。 下 面 是 一 个 包 含 了mouseDown事 件 处 理 的 例 子: import java.awt.*; import java.applet.Applet;
public class canvas extends Applet {
Button b1;
public void init() { // Set our layout as a Border style setLayout(new
BorderLayout(15, 15)); b1 = new Button(\"Test\"); myCanvas c1 = new
myCanvas(100, 100); // add the canvas and the button to the applet
add(\"Center\", c1); add(\"South\", b1); }
public boolean action(Event e, Object arg) { System.out.println(\"Event:
\" + arg); return true; }
public boolean mouseDown(Event e, int x, int y)
{ System.out.println(\"Mouse works: (\" + x + \",\" + y + \")\"); return true; } }
class myCanvas extends Canvas { private int width; private int height;
public myCanvas(int w, int h) { width = w; height = h; reshape(0, 0, w,
h); }
public void paint(Graphics g) { g.setColor(Color.blue); g.fillRect(0, 0,
width, height); }
public boolean mouseDown(Event e, int x, int y) { if (( x < width) && (y
第 七 章 多 线 程
7.1 多 线 程 的 概 念
多 线 程 编 程 的 含 义 是 你 可 将 程 序 任 务 分 成 几 个 并 行 的 子 任 务。 特 别 是 在 网 络 编 程 中, 你 会 发 现 很 多 功 能 是 可 以 并 发 执 行 的。 比 如 网 络 传 输 速 度 较 慢, 用 户 输 入 速 度 较 慢, 你 可 以 用 两 个 独 立 的 线 程 去 完 成 这 ?copy; 功 能, 而 不 影 响 正 常 的 显 示 或 其 他 功 能。 多 线 程 是 与 单 线 程 比 较 而 言 的, 普 通 的WINDOWS采 用 单 线 程 程 序 结 构, 其 工 作 原 理 是: 主 程 序 有 一 个 消 息 循 环, 不 断 从 消 息 队 列 中 读 入 消 息 来 决 定 下 一 步 所 要 干 的 事 情, 一 般 是 一 个 子 函 数, 只 有 等 这 个 子 函 数 执 行 完 返 回 后, 主 程 序 才 能 接 收 另 外 的 消 息 来 执 行。 比 如 子 函 数 功 能 是 在 读 一 个 网 络 数 据, 或 读 一 个 文 件, 只 有 等 读 完 这 ?copy; 数 据 或 文 件 才 能 接 收 下 一 个 消 息。 在 执 行 这 个 子 函 数 过 程 中 你 什 么 也 不 能 干。 但 往 往 读 网 络 数 据 和 等 待 用 户 输 入 有 很 多 时 间 处 于 等 待 状 态, 多 线 程 利 用 这 个 特 点 将 任 务 分 成 多 个 并 发 任 务 后, 就 可 以 解 决 这 个 问 题。
7.1.1 Java线 程 的 模 型
Java的 设 计 思 想 是 建 立 在 当 前 大 多 数 操 作 系 统 都 实 现 了 线 程 调 度。Java虚 拟 机 的 很 多 任 务 都 依 赖 线 程 调 度, 而 且 所 有 的 类 库 都 是 为 多 线 程 设 计 的。 实 时 上,Java支 持Macintosh和Ms-dos 的 平 台 ?reg; 所 以 迟 迟 未 出 来 就 是 因 为 这 两 个 平 台 都 不 支 持 多 线 程。Java利 用 多 线 程 实 现 了 整 个 执 行 环 境 是 异 步 的。 在Java程 序 里 没 有 主 消 息 循 环。 如 果 一 个 线 程 等 待 读 取 网 络 数 据, 它 可 以 运 行 但 不 停 止 系 统 的 其 他 线 程 执 行。 用 于 处 理 用 户 输 入 的 线 程 大 多 时 间 是 等 待 用 户 敲 键 盘 或 击 鼠 标。 你 还 可 以 使 动 画 的 每 一 帧 ?reg; 间 停 顿 一 秒 而 并 不 使 系 统 暂 停。 一 ?copy; 线 程 启 动 后, 它 可 以 被 挂 起, 暂 时 不 让 它 执 行。 挂 起 的 线 程 可 以 重 新 恢 复 执 行。 任 何 时 间 线 程 都 可 以 被 停 止, 被 停 止 的 线 程 就 不 能 再 重 新 启 动。 Java语 言 里, 线 程 表 现 为 线 程 类, 线 程 类 封 装 了 所 有 需 要 的 线 程 操 作 控 制。 在 你 心 里, 必 须 很 清 晰 地 区 分 开 线 程 对 象 和 运 行 线 程, 你 可 以 将 线 程 对 象 看 作 是 运 行 线 程 的 控 制 面 板。 在 线 程 对 象 里 有 很 多 函 数 来 控 制 一 个 线 程 是 否 运 行, 睡 眠, 挂 起 或 停 止。 线 程 类 是 控 制 线 程 行 为 的 唯 一 的 手 段。 一 ?copy; 一 个Java程 序 启 动 后, 就 已 经 有 一 个 线 程 在 运 行。 你 可 通 过 调 用Thread.currentThread 函 数 来 查 看 当 前 运 行 的 是 哪 一 个 线 程。
你 得 到 一 个 线 程 的 控 制 柄, 你 就 可 以 作 很 有 趣 的 事 情, 即 使 单 线 程 也 一 样。 下 面 这 个 例 子 让 你 知 道 怎 样 操 纵 当 前 线 程。 Filename:testthread
class testthread { public static void main(String args[]) { Thread t
=Thread.currentThread(); t.setName(\"This Thread is running\");
System.out.println(\"The running thread:\" + t); try { for (int i=0;i<5;i++)
{ System.out.println(\"Sleep time \"+i); Thread.sleep(1000); }
} catch (InterruptedException e) {System.out.println(\"thread has wrong\"); }
} }
执 行 结 果:java testthread The running thread:Thread[This Thread is running,5,main] Sleep time 0 Sleep time 1 Sleep time 2 Sleep time 3 Sleep time 4
7.1.2 启 动 接 口
一 个 线 程 并 不 激 动 人 心, 多 个 线 程 才 有 实 际 意 义。 我 们 怎 样 创 建 更 多 的 线 程 呢? 我 们 需 要 创 建 线 程 类 的 另 一 个 实 例。 当 我 们 构 造 了 线 程 类 的 一 个 新 的 实 例, 我 们 必 须 告 诉 它 在 新 的 线 程 里 应 执 行 哪 一 段 程 序。 你 可 以 在 任 意 实 现 了 启动 接 口 的 对 象 上 启 动 一 个 线 程。 启 动 接 口 是 一 个 抽 象 接 口, 来 表 示 本 对 象 有 一 ?copy; 函 数 想 异 步 执 行。 要 实 现 启 动 接 口, 一 个 类 只 需 要 有 一 个 叫run的 函 数。 下 面 是 创 建 一 个 新 线 程 的 例 子:
Filename:twothread.java
class twothread implements Runnable { twothread() { Thread t1
=Thread.currentThread(); t1.setName(\"The first main thread\");
System.out.println(\"The running thread:\" + t1); Thread t2 = new
Thread(this,\"the second thread\"); System.out.println(\"creat another
thread\"); t2.start(); try { System.out.println(\"first thread will
sleep\"); Thread.sleep(3000); }catch (InterruptedException e)
{System.out.println(\"first thread has wrong\"); }
System.out.println(\"first thread exit\"); } public void run() { try { for
(int i=0;i<5;i++) { System.out.println(\"Sleep time for thread 2:\"+i);
Thread.sleep(1000); }
} catch (InterruptedException e) {System.out.println(\"thread has
wrong\"); }
System.out.println(\"second thread exit\"); } public static void
main(String args[]) { new twothread(); } }
执 行 结 果:java twothread
The running thread:Thread[The first main thread,5,main] creat another
thread first thread will sleep Sleep time for thread 2:0 Sleep time for
thread 2:1 Sleep time for thread 2:2 first thread exit Sleep time for
thread 2:3 Sleep time for thread 2:4 second thread exit
main线 程 用new Thread(this, \"the second thread\")创 建 了 一 个Thread对 象, 通 过 传 递 第 一 个 参 数 来 标 明 新 线 程 来 调 用this对 象 的run函 数。 然 后 我 们 调 用start函 数, 它 将 使 线 程 从run函 数 开 始 执 行。
7.1.3 同 步
因 为 多 线 程 给 你 提 ?copy; 了 程 序 的 异 步 执 行 的 功 能, 所 以 在 必 要 时 必 须 还 提 ?copy; 一 种 同 步 机 制。 例 如, 你 想 两 个 线 程 通 讯 并 共 享 一 个 复 杂 的 数 据 结 构, 你 需 要 一 种 机 制 让 他 们 相 互 牵 制 并 正 确 执 行。 为 这 个 目 的,Java用 一 种 叫 监 视 器(monitor)的 机 制 实 现 了 进 程 间 的 异 步 执 行。 可 以 将 监 视 器 看 作 是 一 个 很 小 的 盒 子, 它 只 能 容 纳 一 个 线 程。 一 ?copy; 一 个 线 程 进 入 一 个 监 视 器, 所 有 其 他 线 程 必 须 等 到 第 一 个 线 程 退 出 监 视 器 后 才 能 进 入。 这 ?copy; 监 视 器 可 以 设 计 成 保 护 共 享 的 数 据 不 被 多 个 线 程 同 时 操 作。 大 多 数 多 线 程 系 统 将 这 ?copy; 监 视 器 设 计 成 对 象,Java提 ?copy; 了 一 种 更 清 晰 的 解 决 方 案。 没 有Monitor类; 每 个 对 象 通 过 将 他 们 的 成 员 函 数 定 义 成synchronized来 定 义 自 己 的 显 式 监 视 器, 一 ?copy; 一 个 线 程 执 行 在 一 个synchronized函 数 里, 其 他 任 何 线 程 都 不 能 调 用 同 一 个 对 象 的
synchronized函 数。
7.1.4 消 息
你 的 程 序 被 分 成 几 个 逻 辑 线 程, 你 必 须 清 晰 的 知 道 这 ?copy; 线 程 ?reg; 间 应 怎 样 相 互 通 讯。Java 提 了wait和notify等 功 能 来 使 线 程 ?reg; 间 相 互 交 谈。 一 个 线 程 可 以 进 入 某 一 个 对 象 的synchronized 函 数 进 入 等 待 状 态, 直 到 其 他 线 程 显 式 地 将 它 唤 醒。 可 以 有 多 个 线 程 进 入 同 一 个 函 数 并 等 待 同 一 个 唤 醒 消 息。
7.2 Java线 程 例 子
7.2.1 显 式 定 义 线 程
在 我 们 的 单 线 程 应 用 程 序 里, 我 们 并 没 有 看 见 线 程, 因 为Java能 自 动 创 建 和 控 制 你 的 线 程。 如 果 你 使 用 了 理 解Java语 言 的 浏 览 器, 你 就 已 经 看 到 使 用 多 线 程 的Java程 序 了。 你 也 许 注 意 到 两 个 小 程 序 可 以 同 时 运 行, 或 在 你 移 动 滚 动 条 时 小 程 序 继 续 执 行。 这 并 不 是 表 明 小 程 序 是 多 线 程 的, 但 说 明 这 个 浏 览 器 是 多 线 程 的。 多 线 程 应 用 程 序(或applet)可 以 使 用 好 几 个 执 行 上 下 文 来 完 成 它 们 的 工
作。 多 线 程 利 用 了 很 多 任 务 包 含 单 独 的 可 分 离 的 子 任 务 的 特 点。 每 一 个 线 程 完 成 一 个 子 任 务。 但 是, 每 一 个 线 程 完 成 子 任 务 时 还 是 顺 序 执 行 的。 一 个 多 线 程 程 序 允 许 各 个 线 程尽快 执 行 完 它 们。 这 种 特 点 会 有 更 好 的 实 时 输 入 反 应。
7.2.2 多 线 程 例 子
下 面 这 个 例 子 创 建 了 三 个 单 独 的 线 程, 它 们 分 别 打 印 自 己 的\"Hello World\":
//Define our simple threads.They will pause for a short time //and then
print out their names and delay times class TestThread extends Thread
{ private String whoami; private int delay;
//Our constructor to store the name (whoami) //and time to sleep (delay)
public TestThread(String s, int d) { whoami = s; delay = d; }
//Run - the thread method similar to main() //When run is finished, the
thread dies. //Run is called from the start() method of Thread public void
run() { //Try to sleep for the specified time try { sleep(delay); }
catch(InterruptedException e) {} //Now print out our name
System.out.println(\"Hello World!\"+whoami+\"\"+delay); } } /** * Multimtest.
A simple multithread thest program */ public class multitest { public
static void main(String args[]) { TestThread t1,t2,t3; //Create our test
threads t1 = new TestThread(\"Thread1\",(int)(Math.readom()*2000)); t2 =
new TestThread(\"Thread2\",(int)(Math.readom()*2000)); t3 = new
TestThread(\"Thread3\",(int)(Math.readom()*2000));
//Start each of the threads t1.start(); t2.start(); t3.start(); } }
7.2.3 启 动 一 个 线 程
程 序 启 动 时 总 是 调 用main()函 数, 因 此main()是 我 们 创 建 和 启 动 线 程 的 地 方:
t1 = new TestThread(\"Thread1\",(int)(Math.readom()*2000));
这 一 行 创 建 了 一 个 新 的 线 程。 后 面 的 两 个 参 数 传 递 了 线 程 的 名 称 和 线 程 在 打 印 信 息 ?reg; 前 的 延 时 时 间。 因 为 我 们 直 接 控 制 线 程, 我 们 必 须 直 接 启 动 它: t1.start();
7.2.4 操 作 线 程
如 果 创 建 线 程 正 常,t1应 包 含 一 个 有 效 的 执 行 线 程。 我 们 在 线 程 的run()函 数 里 控 制 线 程。 一 ?copy; 我 们 进 入run()函 数, 我 们 便 可 执 行 里 面 的 任 何 程 序。run()好 象main()一 样。
run() 执 行 完, 这 个 线 程 也 就 结 束 了。 在 这 个 例 子 里, 我 们 试 着 延 迟 一 个 随 机 的 时 间(通 过 参 数 传 递?) sleep(delay);
sleep()函 数 只 是 简 单 地 告 诉 线 程 休 息 多 少 个 毫 秒 时 间。
如 果 你 想 推 迟 一 个 线 程 的 执 行, 你 应 使 用sleep()函 数。 当 线 程 睡 眠 是sleep()并 不 占 用 系 统 资 源。 其 它 线 程 可 继 续 工 作。 一 ?copy; 延 迟 时 间 完 毕, 它 将 打 印\"Hello World\"和 线 程 名 称 及 延 迟 时 间。
7.2.5 暂 停 一 个 线 程
我 们 经 常 需 要 挂 起 一 个 线 程 而 不 指 定 多 少 时 间。 例 如, 如 果 你 创 建 了 一 个 含 有 动 画 线 程 的 小 程 序。 也 许 你 让 用 户 暂 停 动 画 至 到 他 们 想 恢 复 为 止。 你 并 不 想 将 动 画 线 程 仍 调, 但 想 让 它 停 止。 象 这 种 类 似 的 线 程 你 可 用suspend()函 数 来 控 制: t1.suspend(); 这 个 函 数 并 不 永 久 地 停 止 了 线 程, 你 还 可 用resume()函 数 重 新 激 活 线 程: t1.resume();
7.2.6 停 止 一 个 线 程
线 程 的 最 后 一 个 控 制 是 停 止 函 数stop()。 我 们 用 它 来 停 止 线 程 的 执 行: t1.stop();
注 意: 这 并 没 有 消 灭 这 个 线 程, 但 它 停 止 了 线 程 的 执 行。 并 且 这 个 线 程 不 能 用t1.start()重 新 启 动。 在 我 们 的 例 子 里, 我 们 从 来 不 用 显 式 地 停 止 一 个 线 程。 我 们 只 简 单 地 让 它 执 行 完 而 已。 很 多 复 杂 的 线 程 例 子 将 需 要 我 们 控 制 每 一 个 线 程。 在 这 种 情 况 下 会 使 用 到stop()函 数。 如 果 需 要, 你 可 以 测 试 你 的 线 程 是 否 被 激 活。 一 个 线 程 已 经 启 动 而 且 没 有 停 止 被 认 为 是 激 活 的。 t1.isAlive() 如 果t1是 激 活 的, 这 个 函 数 将 返 回true.
7.2.7 动 画 例 子
下 面 是 一 个 包 含 动 画 线 程 的applet例 子:
import java.awt.*; import java.awt.image.ImageProducer; import
java.applet.Applet;
public class atest3 extends Applet implements Runnable { Image images[];
MediaTracker tracker; int index = 0; Thread animator;
int maxWidth,maxHeight; //Our off-screen components for double buffering.
Image offScrImage; Graphics offScrGC;
//Can we paint yes? boolean loaded = false;
//Initialize the applet. Set our size and load the images public void init()
[ //Set up our image monitor tracker = new MediaTracker(this);
//Set the size and width of our applet maxWidth = 100; maxHeight =100;
images = new Image[10]; //Set up the double-buffer and resize our applet
try { offScrImage = createImage(maxWidth,maxHeight); offScrGC =
offScrImage.getGraphics(); offScrGC.setColor(Color.lightGray);
offScrGC.fillRect(0,0,maxWidth,maxHeight);
resize(maxWidth,maxHeight); }catch (Exception e)
{ e.printStackTrace(); }
//load the animation images into an array for (int i=0;i<10;i++) { String
imageFile = new String (\"images/Duke/T\" +String.valueOf(i+1) +\".gif\");
images[i] = getImage(getDocumentBase(),imageFile): //Register this
image with the tracker tracker.addImage(images[i],i); } try { //Use
tracker to make sure all the images are loaded tracker.waitForAll(); }
catch (InterruptedException e) {} loaded = true; }
//Paint the current frame. public void paint (Graphics g) { if (loaded)
{ g.drawImage(offScrImage,0,0,this); } }
//Start ,setup our first image public void start() { if (tracker.checkID
(index)) { offScrGC.drawImage (images[index],0,0,this); } animator = new
Thread(this); animator.start(); }
//Run,do the animation work here. //Grab an image, pause ,grab the next...
public void run() { //Get the id of the current thread Thread me =
Thread.currentThread();
//If our animator thread exist,and is the current thread... while
((animatr!= null) && (animator==me)) { if ( tracker.checkID (index))
{ //Clear the background and get the next image
offScrGC.fillRect(0,0,100,100);
offScrGCdrawImage(images[index],0,0,this); index++; //Loop back to the
beginning and keep going if (index>= images.length) { index = 0; } }
//Delay here so animation looks normal try { animator.sleep(200); }catch
(InterruptedException e) {} //Draw the next frame repaint(); } } }
7.3 多 线 程 间 的 通 讯
7.3.1 生 产 者 和 消 费 者
多 线 程 的 一 个 重 要 特 点 是 它 们 ?reg; 间 可 以 互 相 通 讯。 你 可 以 设 计 线 程 使 用 公 用 对 象, 每 个 线 程 都 可 以 独 立 操 作 公 用 对 象。 典 型 的 线 程 间 通 讯 建 立 在 生 产 者 和 消 费 者 模 型 上: 一 个 线 程 产 生 输 出; 另 一 个 线 程 使 用 输 入buffer
让 我 们 创 建 一 个 简 单 的\"Alphabet Soup\"生 产 者 和 相 应 的 消 费 者.
7.3.2 生 产 者
生 产 者 将 从thread类 里 派 生: class Producer extends Thread
{ private Soup soup; private String alphabet = \"
ABCDEFGHIJKLMNOPQRSTUVWXYZ\";
public Producer(Soup s) { //Keep our own copy of the shared object soup
= s; }
public void run() { char c; //Throw 10 letters into the soup for (int
i=0;i<10;i++) { c = alphabet.charAt((int)(Math.random() *26));
soup.add(c); //print a record of osr addition
System.out.println(\"Added\"+c + \"to the soup.\"); //wait a bit before we
add the next letter try { sleep((int)(Math.random() *1000)); } catch
(InterruptedException e) {} } } }
注 意 我 们 创 建 了Soup类 的 一 个 实 例。 生 产 者 用soup.add()函 数 来 建 立 字 符 池。
7.3.3 消 费 者
让 我 们 看 看 消 费 者 的 程 序: class Consumer extends Thread { private Soup soup;
public Consumer (Soup s) { //keep our own copy of the shared object soup
= s; }
public void run() { char c; //Eat 10 letters from the alphabet soup for
(int I=0 ;i<10;i++) { //grab one letter c = soup.eat(); //Print out the
letter that we retrieved System.out.println(\"Ate a letter: \" +c); //try
{ sleep((int)(Math.raddom()*2000)); } catch (InterruptedException e) {} } } }
同 理, 象 生 产 者 一 样, 我 们 用soup.eat()来 处 理 信 息。 那 么,Soup类 到 底 干 什 么 呢?
7.3.4 监 视
Soup类 执 行 监 视 两 个 线 程 ?reg; 间 传 输 信 息 的 功 能。 监 视 是 多 线 程 中 不 可 缺 少 的 一 部 分, 因 为 它 保 持 了 通 讯 的 流 ?copy;。 让 我 们 看 看Soup.java文 件: class Soup { private char buffer[] = new char[6]; private int next = 0; //Flags to keep track of
our buffer status private boolean isFull = false; private boolean isEmpty
= true; public syschronized char eat() { //We can\'t eat if there isn\'t anything
in the buffer while (isEmpty == true) { try { wait() ;//we\'ll exit this
when isEmpty turns false }catch (InterruptedException e) {} } //decrement
the count,since we\'re going to eat one letter next--; //Did we eat the
last letter? if (next== 0) { isEmpty = true; } //We know the buffer can\'t
be full,because we just ate isFull = false; notify(); //return the letter
to the thread that is eating return (buffer[next]); }
//method to add letters to the buffer public synchronized void add(char
c) { //Wait around until there\'s room to add another letter while (isFull
== true ) { try{ wait();//This will exit when isFull turns false }catch
(InterruptedException e) {} } //add the letter to the next available spot
buffer[next]=c; //Change the next available spot next++; //Are we full;
if (next ==6) { isFull =true; } isEmpty =false; notify(); } } soup类 包 含 两 个 重 要 特 征: 数 据 成 员buffer[]是 私 有 的, 功 能 成 员add()和eat()是 公 有 的。
数 据 私 有 避 免 了 生 产 者 和 消 费 者 直 接 获 得 数 据。 直 接 访 问 数 据 可 能 造 成 错 误。 例 如, 如 果 消 费 者 企 图 从 空 缓 冲 区 里 取 出 数 据, 你 将 得 到 不 必 要 的 异 常, 否 则, 你 只 能 锁 住 进 程。 同 步 访 问 方 法 避 免 了 破 坏 一 个 共 享 对 象。 当 生 产 者 向soup里 加 入 一 个 字 母 时, 消 费 者 不 能 吃 字 符, 诸 如 此 类。 这 种 同 步 是 维 持 共 享 对 象 完 整 性 的 重 要 方 面。notify()函 数 将 唤 醒 每 一 个 等 待 线 程。 等 待 线 程 将 继 续 它 的 访 问。
7.3.5 联 系 起 来
现 在 我 们 有 一 个 生 产 者, 一 个 消 费 者 和 一 个 共 享 对 象, 怎 样 实 现 它 们 的 交 互 呢? 我 们 只 需 要 一 个 简 单 的 控 制 程 序 来 启 动 所 有 的 线 程 并 确 信 每 一 个 线 程 都 是 访 问 的 同 一 个 共 享 对 象。 下 面 是 控 制 程 序 的 代 码,SoupTest.java:
class SoupTest { public static void main(String args[]) { Soup s = new
Soup(); Producer p1 = new Producer(s); Consumer c1 = new Consumer(s);
p1.start(); c1.start(); } }
7.3.6 监 视 生 产 者
生 产 者/消 费 者 模 型 程 序 经 常 用 来 实 现 远 程 监 视 功 能, 它 让 消 费 者 看 到 生 产 者 同 用 户 的 交 互 或 同 系 统 其 它 部 分 的 交 互。 例 如, 在 网 络 中, 一 组 生 产 者 线 程 可 以 在 很 多 工 作 站 上 运 行。 生 产 者 可 以 打 印 文 档, 文 档 打 印 后, 一 个 标 志 将 保 存 下 来。 一 个(或 多 个?copy; 消 费 者 将 保 存 标 志 并 在 晚 上 报 告 白 天 打 印 活 动 的 情 况。 另 外, 还 有 例 子 在 一 个 工 作 站 是 分 出 几 个 独 立 的 窗 口。 一 个 窗 口 用 作 用 户 输 入(生 产 者) 另 一 个 窗 口 作 出 对 输 入 的 反 应(消 费 者)。
7.4 线 程API列 表
下 面 是 一 个 常 用 的 线 程 类 的 方 法 函 数 列 表: 类 函 数: 以 下 是Thread的 静 态 函 数, 即 可 以 直 接 从Thread类 调 用。
currentThread 返 回 正 在 运 行 的Thread对 象 yield 停 止 运 行 当 前 线 程, 让 系 统 运 行 下 一 个 线 程 sleep(int n) 让 当 前 线 程 睡 眠n毫 秒 对 象 函 数: 以 下 函 数 必 须 用Thread的 实 例 对 象 来 调 用。
start start函 数 告 诉java运 行 系 统 为 本 线 程 建 立 一 个 执 行 环 境, 然 后 调 用 本 线 程 的run()函 数。 run 是 运 行 本 线 程 的 将 要 执 行 的 代 码, 也 是Runnable接 口 的 唯 一 函 数。 当 一 个 线 程 初 始 化 后, 由start函 数 来 调 用 它, 一 ?copy;run函 数 返 回, 本 线 程 也 就 终 止 了。 stop 让 某 线 程 马 上 终 止, 系 统 将 删 除 本 线 程 的 执 行 环 境 suspend 与stop函 数 不 同,suspend将 线 程 暂 停 执 行, 但 系 统 不 破 坏 线 程 的 执 行 环 境, 你 可 以 用resume来 恢 复 本 线 程 的 执 行 resume 恢 复 被 挂 起 的 线 程 进 入 运 行 状 态 setPriority(int p) 给 线 程 设 置 优 先 级 getPriority 返 回 线 程 的 优 先 级 setName(String name) 给 线 程 设 置 名 称 getName 取 线 程 的 名 称
本 章 小 结:
1.多 线 程 是java语 言 的 重 要 特 点,java语 言 用Thread类 封 装 了 线 程 的 所 有 操 作。 2.线 程 的 接 口 名 为Runnable 3.线 程 间 同 步 机 制 为synchronized关 键 词 4.线 程 间 通 讯 靠wait与notify消 息
第 八 章 Java的\" 异 常\"
\" 异 常\" 指 的 是 程 序 运 行 时 出 现 的 非 正 常 情 况。 在 用 传 统 的 语 言 编 程 时, 程 序 员 只 能 通 过 函 数 的 返 回 值 来 发 出 错 误 信 息。 这 易 于 导 致 很 多 错 误, 因 为 在 很 多 情 况 下 需 要 知 道 错 误 产 生 的 内 部 细 节。 通 常, 用 全 局 变 量errno来 存 储\" 异 常\" 的 类 型。 这 容 易 导 致 误 用, 因 为 一 个errno的 值 有 可 能 在 被 处 理 ?reg; 前 被 另 外 的 错 误 覆 盖 掉。 即 使 最 优 美 的C语 言 程 序, 为 了 处 理\" 异 常\" 情 况, 也 常 求 助 于goto语 句。 Java对\" 异 常\" 的 处 理 是 面 向 对 象 的。 一 个Java的Exception是 一 个 描 述\" 异 常\" 情 况 的 对 象。 当 出 现\" 异 常\" 情 况 时, 一 个Exception对 象 就 产 生 了, 并 放 到 产 生 这 个\" 异 常\" 的 成 员 函 数 里。
8.1 基础
Java的\" 异 常\" 处 理