Java编程思想第4版[中文版](PDF格式)-第128部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
private Button
onOff = new Button(〃Toggle〃);
start = new Button(〃Start〃);
public void init() {
492
…………………………………………………………Page 494……………………………………………………………
add(t);
start。addActionListener(new StartL());
add(start);
onOff。addActionListener(new OnOffL());
add(onOff);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(sp == null)
sp = new SeparateSubTask();
}
}
class OnOffL implements ActionListener {
public vo id actionPerformed(ActionEvent e) {
if(sp != null)
sp。runFlag = !sp。runFlag; // invertFlag();
}
}
public static void main(String'' args) {
Counter2i applet = new Counter2i();
Frame aFrame = new Frame(〃Counter2i〃);
aFrame。addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System。exit(0);
}
});
aFrame。add(applet; BorderLayout。CENTER);
aFrame。setSize(300;200);
applet。init();
applet。start();
aFrame。setVisible(true);
}
} ///:~
这个SeparateSubTask 名字不会与前例中的 SeparateSubTask 冲突——即使它们都在相同的目录里——因为
它已作为一个内部类隐藏起来。大家亦可看到内部类被设为private (私有)属性,这意味着它的字段和方
法都可获得默认的访问权限(run()除外,它必须设为 public,因为它在基础类中是公开的)。除Counter2i
之外,其他任何方面都不可访问private 内部类。而且由于两个类紧密结合在一起,所以很容易放宽它们之
间的访问限制。在SeparateSubTask 中,我们可看到invertFlag()方法已被删去,因为 Counter2i 现在可以
直接访问runFlag。
此外,注意 SeparateSubTask 的构建器已得到了简化——它现在唯一的用外就是启动线程。Counter2i 对象
的句柄仍象以前那样得以捕获,但不再是通过人工传递和引用外部对象来达到这一目的,此时的内部类机制
可以自动照料它。在run()中,可看到对t 的访问是直接进行的,似乎它是SeparateSubTask 的一个字段。
父类中的t 字段现在可以变成 private,因为 SeparateSubTask 能在未获任何特殊许可的前提下自由地访问
它——而且无论如何都该尽可能地把字段变成“私有”属性,以防来自类外的某种力量不慎地改变它们。
无论在什么时候,只要注意到类相互之间结合得比较紧密,就可考虑利用内部类来改善代码的编写与维护。
14。1。3 用主类合并线程
在上面的例子中,我们看到线程类(Thread)与程序的主类(Main )是分隔开的。这样做非常合理,而且易
于理解。然而,还有另一种方式也是经常要用到的。尽管它不十分明确,但一般都要更简洁一些(这也解释
了它为什么十分流行)。通过将主程序类变成一个线程,这种形式可将主程序类与线程类合并到一起。由于
493
…………………………………………………………Page 495……………………………………………………………
对一个 GUI 程序来说,主程序类必须从Frame 或Applet 继承,所以必须用一个接口加入额外的功能。这个接
口叫作Runnable ,其中包含了与Thread 一致的基本方法。事实上,Thread 也实现了Runnable ,它只指出有
一个run()方法。
对合并后的程序/线程来说,它的用法不是十分明确。当我们启动程序时,会创建一个Runnable (可运行
的)对象,但不会自行启动线程。线程的启动必须明确进行。下面这个程序向我们演示了这一点,它再现了
Counter2 的功能:
//: Counter3。java
// Using the Runnable interface to turn the
// main class into a thread。
import java。awt。*;
import java。awt。event。*;
import java。applet。*;
public class Counter3
extends Applet implements Runnable {
private int count = 0;
private boolean runFlag = true;
private Thread selfThread = null;
private Button
onOff = new Button(〃Toggle〃);
start = new Button(〃Start〃);
private TextField t = new TextField(10);
public void init() {
add(t);
start。addActionListener(new StartL());
add(start);
onOff。addActionListener(new OnOffL());
add(onOff);
}
public void run() {
while (true) {
try {
selfThread。sleep(100);
} catch (InterruptedException e){}
if(runFlag)
t。setText(Integer。toString(count++));
}
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(selfThread == null) {
selfThread = new Thread(Counter3。this);
selfThread。start();
}
}
}
class OnOffL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
494
…………………………………………………………Page 496……………………………………………………………
public static void main(String'' args) {
Counter3 applet = new Counter3();
Frame aFrame = new Frame(〃Counter3〃);
aFrame。addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System。exit(0);
}
});
aFrame。add(applet; BorderLayout。CENTER);
aFrame。setSize(300;200);
applet。init();
applet。start();
aFrame。setVisible(true);
}
} ///:~
现在run()位于类内,但它在 init()结束以后仍处在“睡眠”状态。若按下启动按钮,线程便会用多少有些
暧昧的表达方式创建(若线程尚不存在):
new Thread(Counter3。this);
若某样东西有一个 Runnable 接口,实际只是意味着它有一个run()方法,但不存在与之相关的任何特殊东
西——它不具有任何天生的线程处理能力,这与那些从Thread 继承的类是不同的。所以为了从一个
Runnable 对象产生线程,必须单独创建一个线程,并为其传递Runnable 对象;可为其使用一个特殊的构建
器,并令其采用一个Runnable 作为自己的参数使用。随后便可为那个线程调用 start(),如下所示:
selfThread。start();
它的作用是执行常规初始化操作,然后调用run()。
Runnable 接口最大的一个优点是所有东西都从属于相同的类。若需访问什么东西,只需简单地访问它即可,
不需要涉及一个独立的对象。但为这种便利也是要付出代价的——只可为那个特定的对象运行单独一个线程
(尽管可创建那种类型的多个对象,或者在不同的类里创建其他对象)。
注意Runnable 接口本身并不是造成这一限制的罪魁祸首。它是由于 Runnable 与我们的主类合并造成的,因
为每个应用只能主类的一个对象。
14。1。4 制作多个线程
现在考虑一下创建多个不同的线程的问题。我们不可用前面的例子来做到这一点,所以必须倒退回去,利用
从Thread 继承的多个独立类来封装run()。但这是一种更常规的方案,而且更易理解,所以尽管前例揭示了
我们经常都能看到的编码样式,但并不推荐在大多数情况下都那样做,因为它只是稍微复杂一些,而且灵活
性稍低一些。
下面这个例子用计数器和切换按钮再现了前面的编码样式。但这一次,一个特定计数器的所有信息(按钮和
文本字段)都位于它自己的、从Thread 继承的对象内。Ticker 中的所有字段都具有private (私有)属性,
这意味着Ticker 的具体实现方案可根据实际情况任意修改,其中包括修改用于获取和显示信息的数据组件的
数量及类型。创建好一个Ticker 对象以后,构建器便请求一个 AWT 容器(Container)的句柄——Ticker 用
自己的可视组件填充那个容器。采用这种方式,以后一旦改变了可视组件,使用Ticker 的代码便不需要另行
修改一道。
//: Counter4。java
// If you separate your thread from the main
// class; you can have as many threads as you
// want。
import java。awt。*;
import java。awt。event。*;
import java。applet。*;
495
…………………………………………………………Page 497……………………………………………………………
class Ticker extends Thread {
private Button b = new Button(〃Toggle〃);
private TextField t = new TextField(10);
private int count = 0;
private boolean runFlag = true;
public Ticker(Container c) {
b。addActionListener(new ToggleL());
Panel p = new Panel();
p。add(t);
p。add(b);
c。add(p);
}
class ToggleL implements ActionListener {
public void actionPerformed(ActionEvent e) {
runFlag = !runFlag;
}
}
public void run() {
while (true) {
if(runFlag)
t。setText(Integer。toString(count++));
try {
sleep(100);
} catch (InterruptedException e){}
}
}
}
public class Counter4 extends Applet {
private Button start = new Button(〃Start〃);
private boolean started = false;
private Ticker'' s;
private boolean isApplet = true;
private int size;
public void init() {
// Get parameter 〃size〃 from Web page:
if(isApplet)
size =
Integer。parseInt(getParameter(〃size〃));
s = new Ticker'size';
for(int i = 0; i 《 s。length; i++)
s'i' = new Ticker(this);
start。addActionListener(new StartL());
add(start);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(!started) {
started = true;
for(int i = 0; i 《 s。length; i++)
s'i'。start();
}
496
…………………………………………………………Page 498……………………………………………………………
}
}
public static void main(String'' args) {
Counter4 applet = new Counter4();
// This isn't an applet; so set the flag and
// produce the parameter values from args:
applet。isApplet = false;
applet。size =
(args。length == 0 ? 5 :
Integer。parseInt(args'0'));
Frame aFrame = new Frame(〃Counter4〃);
aFrame。addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System。exit(0);
}
});
aFrame。add(applet; BorderLayout。CENTER);
aFrame。setSize(200; applet。size * 50);
applet。init();
applet。start();
aFrame。setVisible(true);
}
} ///:~