Timer を使う

Since 2003.05.21  

前回のプログラムは、点滅をさせる方法について、考え方の基本は間違っていなかったのですが、コンピュータの速度を考慮しませんでした。
人間の視覚特性は、秒16〜24コマ以上の変化には正しい認識ができません。
もっと遅く点滅させなければなりません。

ダミーの処理を間に挟む

イメージとしては、このようになればよいのです。

while (条件) {
    g.fillRect(0, 0, dim.width, dim.height);
    時間のかかる処理
    g.drawImage(starimage, 0, 0, this);
    時間のかかる処理
}

動くコードとして書くと、次のような感じです。(コードの中の数字、数式に意味はありません)

private long Dummy(long x, long y) {
    for(int c = 0; c < 10000; c++) {
        long z = x * y;
        x = x + 3;
        y = z;
    }
    return z;
}

public void paint(Graphics g) {
    long p = 1;
    long q = 2;
    long r = 3;
    dim = getSize();
    g.setColor(backcolor);
    while (runnable) {
        g.fillRect(0, 0, dim.width, dim.height);
        p = Dummy(q, r);
        g.drawImage(starimage, 0, 0, this);
        q = Dummy(p, r);
    }
}

コードの中の「10000」の数字を適当に設定すると、今回の完成プログラムと同じように見えるプログラムになります。
コンピュータの世界での大昔には、このようなコードを書かなければならないケースも有りました。
但し、このコードには欠点があります。

  1. 速度の違うコンピュータで実行したり、他の処理を同時に実行したりすると、点滅の間隔が異なってしまう
  2. 何よりも、CPUを 100% 使い切ることの対策がされていない

回り道をしましたが、Javaにはこのようなケースに対処するため、プログラムの実行を飛び飛びにして、不要な時間は休ませておく「Runnable」インタフェースというものが用意されています。(もうひとつ、Timerクラスというのもあります)
次々項では、その「Runnable」インタフェースの説明をします。
ここでは、プログラムのコードは一つではないこと。常に、「他のロジック、コーディング方法が無いか」を考察することが大切だと知っていただきたいために、回り道をしました。

インタフェース

「Runnable」インタフェースを使うといいましたが、まず「インタフェース」について説明しないといけません。
「クラス」については基礎編4で解説しました。「差分プログラミング」についても解説しました。「継承」を使えば雛形クラスの全機能が使えます。
しかし、これだけでは対処できないケースがあります。2つ以上のクラスで定義されているメソッドを使いたいという場合があります。今回の場合であれば、「Applet」クラスを継承するのは、Appletを作るのですから必須です。さらに時間制御の機能を果たすメソッドも必要です。C++などでは、「多重継承」という仕組みが用意されていて、複数のクラスを継承できます。この「多重継承」は、実際に使うと複雑怪奇で、C++を難解にしている大きな要素です。Javaでは継承は「単一継承」に限り複雑さを回避しています。
Javaでは、いろんな場面でよく使われるメソッド群をグループ化しておいて、必要な時に追加するという方法を取ります。このグループ化したメソッド群を「インタフェース」と呼びます。

Runnable インタフェースを使う

前々項では、時間を経過させるために、時間のかかる処理を間に挟みました。
ここでは、「果報は寝て待て」で、プログラムの進行を一定時間止めてしまう方法を取ります。 「目覚まし時計」は、JavaVMに役割を振ります。
Runnable インタフェースはシンプルで、メソッドは「run()」だけです。Runnable インタフェースを使うプログラム(クラス)は、自身を「Thread」としてJavaVMに登録します。そうするとあるタイミングで「run()」メソッドが呼び出されます。そこで必要な処理を実行し、その最後で「Thread.sleep(long)」を実行すると、指定時間休止後、再度、「run()」メソッドが呼び出されることになります。
アプレットの場合、実行開始の通知を受ける「start()」メソッドで「Thread」登録をし、実行停止の通知を受ける「stop()」メソッドで、「Thread」の実行を止めるようにします。
Runnable インタフェースをインプリメントすると、簡単に「マルチスレッド」にすることができます。「マルチスレッド」は、直訳どおり、同時に複数の同じアプレットを動かすことです。ですから、このStar3アプレットの星は、単一画面上に複数表示させることができます。

少し細かくコードを見ていきます。関係するのは、この色の部分です。
アプレットは、ロードされるとinitメソッドが呼ばれます。その後、実行を開始するタイミングでstartメソッドが呼ばれます。
星の画像ファイルが存在すれば、runnable は true になっていますので、72行目以降のコードが実行されます。running に true をセットし、スレッドとして実行するために、Threadクラスのstartメソッドを呼びます。
そうすると、次にはRunnableインターフェースのrunメソッドが呼ばれますので、その中で、3つの処理をします。

アプレットを終わらせる必要が生じたときには、stopメソッドが呼び出されます。
stopでは running に false がセットされるだけです。
running に false がセットされると、runメソッドの while ループを抜けることになりますので、最終行(67行目)にある「timer = null;」が実行されます。Thread参照を無効にしているのですが、これは、ガーベジコレクタに実行の終わった不要コードを認識させるためです。

ガーベジコレクタ

プログラムは、実行の途中で一時的なメモリを必要とするときが良くあります。複雑な計算をするときの途中結果の格納、データの並べ替えの時の作業領域などです。
作業が終わってそのままにしておくと、使われない領域がどんどん増えて、メモリを圧迫します。他の多くの言語では、このために使用の終わった領域の返却を書かなければなりません。
ガーベジコレクタというプログラムは、定期的に実行され、使われなくなったメモリ領域を探して回収します。Java はガーベジコレクタを備えた数少ないプログラム言語の1つです。そのため、Java では一時メモリの処理に神経をあまり使わなくてすみます。

その他のプログラムの説明

この色の部分が、その他の追加した部分です。
47-50行目は、点滅の間隔をパラメータで受け取るコードです。パラメータには、1秒間の点滅回数の指定をすることができます。
53-56行目は、無地の描画を毎回関数呼び出しするのでなく、無地画像を用意しておき、その無地画像を描画するためのコードです。
アプレットサイズと同じ大きさのImageオブジェクトを作成し、backColorで全面を塗りつぶしておきます。

ソースコード

java/net/sys5jp/basic/Star3.java

1 package net.sys5jp.basic;
2
3 import java.applet.*;
4 import java.awt.*;
5
6 public class Star3 extends Applet implements Runnable {
7     boolean runnable = true;
8     boolean running = false;
9     Thread timer;
10     int maxstage = 1;
11     int stage = 0;
12     long sleep = 50;

13     Color backColor = new Color(0, 0, 0);
14     Dimension dim;
15     Image starImage;
16     Image backImage;
17
18     private Color getColorParameter(String param) {
19         String value = getParameter(param);
20         try { return new Color(Integer.parseInt(value, 16)); }
21         catch(Exception e) { return null; }
22     }
23
24     private int getIntParameter(String param) {
25         String value = getParameter(param);
26         try { return Integer.parseInt(value, 10); }
27         catch(Exception e) { return 0; }
28     }
29
30     public void paint(Graphics g) {
31         if (stage == 0) {
32             g.drawImage(backImage, 0, 0, this);
33         }
34         else {
35             g.drawImage(starImage, 0, 0, this);
36         }
37     }
38
39     public void init() {
40         Color color;
41         if((color = getColorParameter("backcolor")) != null) {
42             backColor = color;
43         }
44         try { starImage
45                 = getImage(getDocumentBase(), getParameter("starimage")); }
46         catch(Exception e) { runnable = false; }
47         int cc;
48         if((cc = getIntParameter("speed")) > 0) {
49             sleep = 1000 / cc;
50         }
51         setBackground(backColor);
52         dim = getSize();
53         backImage = createImage(dim.width, dim.height);
54         Graphics bg = backImage.getGraphics();
55         bg.setColor(backColor);
56         bg.fillRect(0, 0, dim.width, dim.height);
57     }
58
59     public void run() {
60         while(running) {
61             repaint();
62             if (stage >= maxstage) { stage = 0; }
63             else { stage++; }
64             try { Thread.sleep(sleep); }
65             catch (InterruptedException e) {}
66         }
67         timer = null;
68     }
69
70     public void start() {
71         if (runnable) {
72             running = true;
73             if (timer == null) {
74                 timer = new Thread(this);
75                 timer.start();
76             }
77         }
78     }
79
80     public void stop() { running = false; }
81
82 }

java/html/Star3.html

<html>
<head>
<style>
body {background-color:rgb(0, 0, 0);
    color:rgb(255, 255, 255)}
table {text-align:center;
    margin:10px 10px}
td {padding:5px 10px}
</style>
</head>
<body>
<table>
<tr><td>
<applet code="net/sys5jp/basic/Star1.class"
    codebase="../"
    width=30 height=30>
        <param name="starimage" value="../images/Star30.png">
</applet>
</td><td>
<applet code="net/sys5jp/basic/Star3.class"
    codebase="../"
    width=30 height=30>
        <param name="starimage" value="../images/Star30.png">
</applet>
<tr><td>
Star1.class
</td><td>
Star3.class
</td></tr>
</table>
</body>
</html>

トップヘ 目次ヘ 前ヘ 次ヘ