Home: Up

Javaプログラムの作り方


4.JavaでのGUIアプリケーション

そろそろ実行結果に変化を付けてみたいと思います.
ここでは、javaでのGUIアプリケーションの作り方について記述していきます.
CでWindowsのアプリを書いてたころと比べて「何でこんなに簡単なの」と思ってしまうはず. (Cで書いた事のある人はあんまりいないか)


4−1.いわゆる"HelloWorld"系プログラム(GUI版)

以下は3分で作れるHelloWorldGUI版です.

import java.awt.*;
/**
 * お勉強用テストアプリケーション4_1.
 * <p>
 * フレーム上にHelloWorldを表示します.
 * @author Shin
 * @version 1.0
 */
class Test4_1 {
    /**
     * 実行開始時に呼び出されるメソッド.
     * @param args コマンドラインパラメタ文字列群
     */
    public static void main(String[] args){
        //フレームウインドウオブジェクトを生成します.
        Frame           frame = new Frame("JavaでHelloWorld");
        //ラベル(Windowsで言うところのスタティックテキスト)を生成します.
        Label           label = new Label("Hello World!!!");

        //フレームにテキストラベルを貼り付けます.
        frame.add(label);

        //ウィンドウリソースの作成+ウィンドウサイズ調整
        frame.pack();

        //ウィンドウの表示
        frame.setVisible(true);

        //もちろんコンソールへの出力も健在です
        System.out.println("こっちでもHello World!!");
    }
}

プログラムは実質5行しかありません.これを実行するとウィンドウが現れてウインドウ内に "Hello World!!!"が表示されます.

プログラムの説明ですが、まず、先頭のimport文については、先に少し説明したように、 「java.awt.何とか」と言うクラスを使用するのでソースコード内では「java.awt.」を省略させて! という意味です.
ではmain()メソッドの一行目から見てみましょう.
まず(java,awt,)Frameクラス型のローカル変数を宣言し、初期値として、new Frame();を用いて フレームオブジェクトを生成/代入しています.
Frame();というのが「コンストラクタ」の呼び出し文である事は覚えているでしょうか?
この文では、Frame()コンストラクタのパラメタとして、"JavaでHelloWorld"という文字列 を渡しています.
Frameクラスのリファレンスを見れば解りますが、このように文字列を指定してコンストラクタを呼び出す事で フレームのタイトルバー文字列を指定した事になります.
次の行のLabalクラスについても同じような処理ですが、こちらはラベルというコントロールに 表示する文字列を指定してjava.awt.Labelクラスのオブジェクトを生成しています.
(コンストラクタのパラメタの意味はメソッドと同様にクラス毎に異なります. これはクラスのリファレンス(またはソース)を見るしかないでしょう)

その後の3行はすべてFrameクラスのインスタンスメソッド (通常のメソッドをクラスメソッドと区別する時にこう呼びます)を呼び出しています.
一つ目はFrame#add()メソッドの呼び出しです.
これはフレーム(ウィンドウの事です)に対して、部品(コンポーネント:Component)を貼り付けるメソッドです.
ここではラベルオブジェクトを貼り付けています.

ここでラベルオブジェクトの事をコンポーネントと呼びましたが、フレームオブジェクトの方は 「コンテナ:Container」と呼びます.
「コンポーネントをコンテナにaddする」等といいます.

次はFrame#pack()メソッドの呼び出しです.
このメソッド内で何をやるかを説明すると長くなるので、ここではフレーム(コンテナ)上の部品を 可能な限り小さく詰め込むものと思ってください.
(後ウィンドウを表示する前に必要な処理なんです)
その次のFrame#setVisible()メソッドはtrueを与える事でそのコンポーネントを表示します.

ここでまた・・
今度はフレームオブジェクトの事を「コンポーネント」といっています.
フレームはコンテナなのにコンポーネントなの?というと実はそのとおりです.
コンテナの定義は「コンポーネントを配置可能なコンポーネント」という事ですので・・

これだけでウィンドウが表示されて、内部にラベル(=テキストコントロール)が表示されます.
(最後の行はただコンソールにメッセージを表示するだけですので無くてもかまいません)


4−2.さらに部品を置いてみる

部品を増やしてアプリケーションらしくしてみましょう.

import java.awt.*;
/**
 * お勉強用テストアプリケーション4_2のメイン.
 * <p>
 * 2つのTest4_2フレームを表示します.
 * @author Shin
 * @version 1.0
 */
class Test4_2Main {
    /**
     * 実行開始時に呼び出されるメソッド.
     * @param args コマンドラインパラメタ文字列群
     */
    public static void main(String[] args) {
        //2つのTest4_2オブジェクトを生成します.
        Test4_2         frame1  = new Test4_2();
        Test4_2         frame2  = new Test4_2();

        //Test4_2クラスのメソッドを呼び出します
        frame1.show();
        System.out.println(Test4_2.frameCount_+"個目のフレームが作成されました");
        frame2.show();
        System.out.println(Test4_2.frameCount_+"個目のフレームが作成されました");
    }
}
/***************************************************************************/
/**
 * お勉強用テストアプリケーション4_2.
 * <p>
 * フレーム上にエディットコントロールと
 * ボタンを表示します.<br>
 * @author Shin
 * @version 1.0
 */
class Test4_2 {
    //フィールド(メンバ)です
    public static int     frameCount_ = 0;
    private Frame         frame_;
    private TextField     textField_;
    private Button        button_;
    private Label         label_;

    /**
     * コンストラクタではフレームを構築します.
     * <p>
     * オブジェクト自体は変数の初期化時に生成されます.<br>
     * この初期化タイミングはコンストラクタが呼び出される直前です.
     */
    Test4_2() {
        //各オブジェクトを生成します
        frame_      = new Frame();
        textField_  = new TextField("入力フィールド");
        button_     = new Button("ボタンを押すと転送します");
        label_      = new Label("ここに転送されます");

        //レイアウトマネージャを設定します.(横並びです)
        frame_.setLayout(new FlowLayout());
        //フレームにテキストフィールドを貼り付けます.
        frame_.add(textField_);
        //フレームにボタンを貼り付けます.
        frame_.add(button_);
        //フレームにテキストラベルを貼り付けます.
        frame_.add(label_);
    }

    void show() {
        //staticフィールド(クラス変数)をカウントアップします.
        frameCount_++;
        frame_.setTitle("フレーム"+frameCount_);
        //ウィンドウリソースの作成+ウィンドウサイズ調整
        frame_.pack();
        //ウィンドウの表示
        frame_.setVisible(true);
    }
}

このプログラムを実行すると、ウィンドウが2つ表示されます.
それぞれのウィンドウには、入力フィールドとボタンおよびラベル(テキスト表示域) が存在します.これらはすべてAWT(Abstract Window Toolkit:抽象ウィンドウツールキット) の定義済みのコンポーネントです.

コメントを除けばこれも(実行文は)10数行のプログラムです.
クラスを2つに分けています.
Test4_2Mainクラスのmain()メソッドでは、Test4_2クラスのオブジェクトを 2つ生成して、それぞれについてshow()メソッドを呼び出しているだけです.
ではTest4_2クラスのオブジェクトとは何をしているのでしょう.

まずインスタンス生成時の処理であるコンストラクタ(Test4_2()メソッド)が実行されます.
その最初の4行はTest4_1クラスと同様で、それぞれのクラス型フィールドに対して、 インスタンスオブジェクトを生成/代入しています.
前にも説明したとおり、コンストラクタのパラメタの意味はクラス毎に異なります.
今回の場合はすべて文字列を渡しており、それぞれのコンポーネントに表示される文字列を指定する事になります.

次のFrame#setLayout()メソッドについてですが、これはadd()と同様にコンテナ(Container)用のメソッドであり、 そのコンテナ内のコンポーネント配置方法を設定するメソッドです.
ここでは詳しく説明しませんが、FlowLayoutオブジェクトを渡す事で、add()した順番に左から横並びに配置されるという意味になります.

その後の3行は前項で説明したとおり、コンテナに3つのコンポーネントを貼り付けています.
この場合、setLayout()の指定によりaddする順番に意味がある事だけは注意しておいてください.
これでコンストラクタはおしまいです.

次に呼び出されるメソッドshow()の一行目でTest4_2のクラス変数frameCount_を インクリメントしています.これかどのような操作になるかはstaticの性質を理解 していればわかると思います.

この例のようにそのクラスのオブジェクトの個数を数えたりするのは クラス変数の典型的な使用例です.
他にも、全インスタンスから共通の情報を参照したい(見たい)ときに クラス変数を利用します.

次の行のFrame#setTitle()はフレームのタイトルバー文字列を設定するものです.
(Frameのコンストラクタに指定した場合は、内部でこのメソッドが呼ばれます)
タイトルバー文字列として「"フレーム"+frameCount_」という式を渡しています.
この式はframeCount_が1であれば"フレーム1"という文字列になります.

+演算子とString型の扱い

JavaではStringオブジェクト同士の+演算子による結合を言語仕様でサポートしています.
Stringオブジェクトとはjava.lang.Stringクラスのオブジェクトのことですが、 このオブジェクトについてだけは言語のサポートによる他のクラスと異なる特性を いくつか持っています.
たとえば文字列リテラル(""で囲んだ文字列)が暗にnew String("")に置換されるため、 コード中で直接文字列リテラルを使用できる事や、前記の+演算子による結合などです.
詳しくは「Java言語仕様」を参照してください.

その後の2行はTest4_1と同様なフレームの表示処理です.

実はshow()メソッドの内容をすべてコンストラクタ内に記述しても同じように 動作するのですが、コンストラクタは先にも述べたとおり、オブジェクトの初期化 を行う事が基本であり、画面表示に影響するような処理は入れない方が良い ため別メソッドにしています.
(タイトルバー文字列の設定などはコンストラクタ側に書いてもいいのですが・・)


4−3.入力を受け付けます

前のプログラムをユーザの入力を処理するように改造してみます.
ただし、ここでは「ウィンドウを閉じる」アクションにしか応答しません.

import java.awt.*;
import java.awt.event.*;
/**
 * お勉強用テストアプリケーション4_3のメイン.
 * <p>
 * 2つのTest4_3フレームを表示します.
 * @author Shin
 * @version 1.0
 */
class Test4_3Main {
    /**
     * 実行開始時に呼び出されるメソッド.
     * @param args コマンドラインパラメタ文字列群
     */
    public static void main(String[] args) {
        //2つのTest4_3オブジェクトを生成します.
        Test4_3         frame1  = new Test4_3();
        Test4_3         frame2  = new Test4_3();

        //Test4_3クラスのメソッドを呼び出します
        frame1.show();
        System.out.println(frame1.frameCount_+"個目のフレームが作成されました");
        frame2.show();
        System.out.println(frame2.frameCount_+"個目のフレームが作成されました");
    }
}
/***************************************************************************/
/**
 * フレームを出すクラス4_3.
 * <p>
 * フレーム上にエディットコントロールと
 * ボタンを表示します.<br>
 * @author Shin
 * @version 1.0
 */
class Test4_3 {
    //フィールド(メンバ)です
    public static int     frameCount_ = 0;
    private Frame         frame_;
    private TextField     textField_;
    private Button        button_;
    private Label         label_;

    /**
     * コンストラクタではフレームを構築します.
     * <p>
     * オブジェクト自体は変数の初期化時に生成されます.<br>
     * この初期化タイミングはコンストラクタが呼び出される直前です.
     */
    public Test4_3() {
        //各オブジェクトを生成します
        frame_      = new Frame();
        textField_  = new TextField("入力フィールド");
        button_     = new Button("ボタンを押すと転送します");
        label_      = new Label("ここに転送されます");

        /**********************************************************/
        /* ウィンドウイベント用のアダプタクラスを登録します.      */
        /**********************************************************/
        frame_.addWindowListener(new Test4_3WindowAdapter(this));
        //レイアウトマネージャを設定します.(横並びです)
        frame_.setLayout(new FlowLayout());
        //フレームにテキストフィールドを貼り付けます.
        frame_.add(textField_);
        //フレームにボタンを貼り付けます.
        frame_.add(button_);
        //フレームにテキストラベルを貼り付けます.
        frame_.add(label_);
    }

    /**
     * フレームの表示を行います.
     * <p>
     * 普通は初期化はコンストラクタに書き、それ以外は
     * 他のメソッドで処理します.<br>
     * この処理をコンストラクタ内に書いても動く事は動きますが
     */
    public void show() {
        //staticフィールド(クラス変数)をカウントアップします.
        frameCount_++;
        frame_.setTitle("フレーム"+frameCount_);
        //ウィンドウリソースの作成+ウィンドウサイズ調整
        frame_.pack();
        //ウィンドウの表示
        frame_.setVisible(true);
    }
    /**
     * フレームの閉じる時にアダプタクラスから呼び出されます.
     * <p>
     * フレームの破棄を行います.<br>
     */
    public void windowClosing() {
        //フレームを破棄します
        frame_.dispose();
    }
}
/***************************************************************************/
/**
 * Test4_3内のFrameクラス用のWindowAdapterです.
 * <p>
 * フレームへのWindowEvent(ウィンドウ閉じる)が発生したら、
 * windowClosed()メソッドが呼び出されます.
 * @author Shin
 * @version 1.0
 */
class Test4_3WindowAdapter extends WindowAdapter {
    private Test4_3       owner_;

    /**
     * コンストラクタです.
     * <p>
     * イベントがこのクラスに渡される為、実際の処理を行う
     * オーナーを保持します.<br>
     * @param owner イベントを転送するオーナーオブジェクト(Test4_3)
     */
    public Test4_3WindowAdapter(Test4_3 owner) {
        owner_  = owner;
    }

    /**
     * ユーザがWindowを閉じようとしたら呼ばれます.
     * <p>
     * @param ev ウィンドウイベントオブジェクト(ここでは参照しません)
     */
    public void windowClosing(WindowEvent ev) {
        owner_.windowClosing();
    }
}

ちょっとコメントが多いせいもありますが長くなってきてしまいました.
クラスは全部で3つです.特に3つ目のクラスは新しいキーワードが出てきます.

1つ目のクラスTest4_3MainはTest4_2Mainとまったく同じ事をしています.
異なる部分は生成するオブジェクトがTest4_3クラスのインスタンスになっているだけです.

このようにクラス名を明示してインスタンスを生成すると、生成したいクラスを入れ替えたい時に このプログラムのように作り替える必要が出てきます.
※Cプログラムでも同様の問題が発生します.
しかし、オブジェクト指向ではこのインスタンス生成部分を抽象化して、クラス名を明示しないでオブジェクトを得る ようにコーディングする事もできます.(いずれその方法についても説明します)

2つ目のクラスTest4_3もTest4_2とほとんど変わりませんが、数行だけ異なる個所があります.
まず、以下の行を見てください.

frame_.addWindowListener(new Test4_3WindowAdapter(this));

これは生成されたFrameクラスのインスタンスメソッドaddWindowListener()の呼び出し文です.
パラメタとしてTest4_3WindowAdapterクラスのインスタンスを渡している事になります.
(オブジェクト生成と同時にそれをメソッドに渡しています)
このメソッドの意味は「フレームのイベント配送システムに対し、指定したイベントリスナを登録する」です.

WindowListenerとはWindowEventと呼ばれる一連のイベント群(「Windowを閉じる」など)を受信する事ができる という性質を持ったオブジェクトです.
このメソッドを呼び出した時点から、ユーザなどによりWindowEventが発生した場合に、WindowListener に対して通知(WindowListenerのメソッドが呼ばれる事)されるようになります.
WindowListenerとして指定しているのは3つ目のTest4_3WindowAdapterクラスのインスタンスなのですが、 これについては後で説明します.

Test4_3クラスはこの他に、windowClosing()というメソッドを追加しています.
このメソッドは後で説明するTest4_3WindowAdapterから呼び出されるメソッドで、名前からわかる通り Windowを閉じようとした時に呼び出されます.
処理自体はFrame#dispose()メソッドによりframe_フィールドが参照しているフレームオブジェクトを破棄 しているだけです.

次に問題のTest4_3WindowAdapterクラスについて説明します.

先ほど説明した通り、このクラスはWindowListenerとして機能します.
WindowListenerとはインターフェイスと呼ばれるものであり、 クラスではありません.そしてWindowListenerインターフェイスには、上にも示した通り、 「WindowEventを受信できる性質」が記述されています.
具体的には以下のように定義されています.

public interface WindowListener extends EventListener {
    public abstract void windowOpened(WindowEvent e);
    public abstract void windowClosing(WindowEvent e);
    public abstract void windowClosed(WindowEvent e);
    public abstract void windowIconified(WindowEvent e);
    public abstract void windowDeiconified(WindowEvent e);
    public abstract void windowActivated(WindowEvent e);
    public abstract void windowDeactivated(WindowEvent e);
}

extends 云々は後回しにしてこの宣言を見てみると、クラスの宣言と似ているのですが、全てメソッドの 実体(処理)が記述されていません.
これは抽象メソッド(abstractキーワードのついたメソッド)と呼ぶのですが、そのような事は置いといても 「実体が無い=どこかで実体を書かなければならない」、「WindowListenerのメソッドを呼ぶ人がどこかにいる」 という2点から、先ほどの「イベントを受信できる性質」と述べた事の意味がぼんやりとわかるかなと思います.

そして、やっとTest4_3WindowAdapterの説明なのですが、ここでもちょっとextends 云々は置いといて先に実装(中身) の方を見てみましょう.
このクラスにはコンストラクタとwindowClosing()というメソッドが定義されています.
コンストラクタはオブジェクト生成時に呼ばれ、Test4_3クラスのインスタンスをパラメタに取って、 それをインスタンスフィールドに設定する事から、オブジェクト1つにつき1つのTest4_3オブジェクトを 管理するクラスという事になります.
そしてwindowClosing()メソッドが呼び出された場合、管理しているTest4_3クラスのインスタンスメソッド であるwindowClosing()を呼び出します.

このクラスの実装はこれだけです.
ではextendsというキーワードでどうやってWindowListenerの性質を持たせているかを説明していきましょう.

extendsキーワードは「継承」を表すキーワードです.その後ろに記述された1つのクラスの全ての フィールドと全てのメソッドがそのクラスに「実装」されます.
つまりWindowAdapterというのはクラス名なんですね.じゃあWindowAdapterというクラスはどんなクラスでしょう.
以下にWindowAdapterのクラス宣言を記します.

public abstract class WindowAdapter implements WindowListener {
    public void windowOpened(WindowEvent e) {}
    public void windowClosing(WindowEvent e) {}
    public void windowClosed(WindowEvent e) {}
    public void windowIconified(WindowEvent e) {}
    public void windowDeiconified(WindowEvent e) {}
    public void windowActivated(WindowEvent e) {}
    public void windowDeactivated(WindowEvent e) {}
}

また新しいキーワードが目白押しですが、クラス宣言自体は先ほど挙げたWindowListenerインターフェイスに 良く似ています.というよりまったく同じメソッドが定義されています.
WindowListenerインターフェイスと異なるのは、各メソッドの実装が記述されている事です.
(全て中身は空ですが−";"と"{}"の違いに注意してください)

クラスに対してのabstractというのはそのクラスをインスタンス化する事を禁止するという意味です.(ここでは深く考えません)
そしてimplementsというキーワードが、先ほどから強調している「イベントを受信できる性質」の「実装」 を意味しています.

ここではあまり詳しく説明しませんが、言葉にすると以下のような感じで表されます.
「WindowListenerインターフェイスを実装したWindowAdapterクラスから継承したTest4_3Adapterクラスは WindowListenerの性質を持っています」
継承の話はオブジェクト指向プログラミングの方で詳しく行っています.

そして、Test4_3WindowAdapterクラスで定義しているwindowClosing(WindowEvent)というメソッドはWindowListener で宣言されたメソッドですが、WindowAdapterクラスで既に実装されています.
つまり、それを継承したTest4_3WindowAdapterには何もしないwindowClosing()メソッドも継承されているのですが、 再度定義し直しているわけです.
これを「オーバーライド」と呼びます.
そして、通常WindowAdapterはウィンドウを閉じようとした時に呼ばれるwindowClosing()で何もせずに復帰するところを オーバーライドする事でTest4_3WindowAdapter独自の処理(ウィンドウの破棄)をさせているわけです.

なぜaddWindowListenerメソッドにTest4_3WindowAdapterクラスのオブジェクトを渡せるのかという疑問が 沸いている事と思いますが、ここでは深くは説明しません.(すごい重要ですが)
キーワードは「インターフェイスの実装や継承は型を継承させる役割がある」という事です.
Java言語仕様を参照してください.

4−4.一応完成させてみます

一通りの入力イベント応答を記述してこのサンプルを完成させてみました.
ソースが長くなるので今回はコメントを省いてみました(たいして長くないと思いませんか?).

import java.awt.*;
import java.awt.event.*;

/**
 * お勉強用テストアプリケーション4_4.
 * <p>
 * フレーム上にエディットコントロールと
 * ボタンを表示します.<br>
 * @author Shin
 * @version 1.0
 */
class Test4_4Main {
    public static void main(String[] args) {
        Test4_4         frame1  = new Test4_4();
        Test4_4         frame2  = new Test4_4();
        frame1.show();
        System.out.println(Test4_4.frameCount_+"個目のフレームが作成されました");
        frame2.show();
        System.out.println(Test4_4.frameCount_+"個目のフレームが作成されました");

        while (Test4_4.frameCount_>0);
        System.exit(0);
    }
}
/***************************************************************************/
class Test4_4 {
    public static int     frameCount_ = 0;
    private Frame         frame_;
    private TextField     textField_;
    private Button        button_;
    private Label         label_;

    public Test4_4() {
        frame_      = new Frame();
        textField_  = new TextField("入力フィールド");
        button_     = new Button("ボタンを押すと転送します");
        label_      = new Label("ここに転送されます");

        frame_.addWindowListener(new Test4_4WindowAdapter(this));
        button_.addActionListener(new Test4_4ActionAdapter(this));
        frame_.setLayout(new FlowLayout());
        frame_.add(textField_);
        frame_.add(button_);
        frame_.add(label_);
    }

    public void show() {
        frameCount_++;
        frame_.setTitle("フレーム"+frameCount_);
        frame_.pack();
        frame_.setVisible(true);
    }

    public void windowClosing() {
        frame_.dispose();
        frameCount_--;
    }

    public void actionPerformed() {
        label_.setText(textField_.getText());
        textField_.setText("");     //テキストフィールドをクリアします
    }
}
/***************************************************************************/
class Test4_4WindowAdapter extends WindowAdapter {
    private Test4_4       owner_;

    public Test4_4WindowAdapter(Test4_4 owner) {
        owner_  = owner;
    }

    public void windowClosing(WindowEvent ev) {
        owner_.windowClosing();
    }
}
/***************************************************************************/
class Test4_4ActionAdapter implements ActionListener {
    private Test4_4       owner_;

    public Test4_4ActionAdapter(Test4_4 owner) {
        owner_  = owner;
    }

    public void actionPerformed(ActionEvent ev) {
        owner_.actionPerformed();
    }
}

クラスは4つになりましたが、特に新しい処理はありません.
Java CoreAPIのメソッドでいくつか新しいものが出てきていますが、みんな名前から機能を想像できるものと思います.

前のプログラムに加えて、プッシュボタンのActionEventに応答する処理が追加されています.
button_.addActionListener(new Test4_4ActionAdapter(this));
という文によって、ボタンクラスにTest4_4ActionAdapterクラスのオブジェクトを登録しています.
これによって、プッシュボタンが押下された場合に登録されたリスナ(Test4_4ActionAdapter)のactionPerformed()メソッドが呼び出されるようになります.

このドキュメントでは、リスナオブジェクトからメインオブジェクトに処理を委譲する書き方ばかりしていますが、オブジェクト指向的にはあまりいい方法とはいえないので気をつけてください.
この書き方では、何のためにイベント処理のためのクラスを用意しているのかよくわからないですもんね.
機会があれば、このあたりの説明も書きたいと思いますが、一概にどうするべきだともいえないので躊躇してます.

Under Construction


4−5.描画してみましょう

GUIアプリケーションやアプレットでは、コンポーネント(ここではボタンやウィンドウなどの部品のことです)自身に文字列を表示する機能を持っていない限り、文字も自分で描画する必要があります.
逆に言えば、コンソールに表示される「文字」とは異なり、表示位置や色/フォント等のスタイルを細かく指定できるわけです.
というわけで、さっそく文字列を描画するアプリケーションを紹介します.

import java.awt.*;
import java.awt.event.*;

/**
 * お勉強用テストアプリケーション4_5.
 * <p>
 * フレーム上のパネルに対して文字列を描画します.<br>
 * @author Shin
 * @version 1.0
 */
class Test4_5Main {
    public static void main(String[] args) {
        Test4_5 frame1 = new Test4_5();
        frame1.show();
    }
}
/***************************************************************************/
class Test4_5 {
    private Frame frame_;
    private Test4_5Canvas canvas_;

    public Test4_5() {
        frame_ = new Frame("描画してみます");
        canvas_ = new Test4_5Canvas("描画する文字列はこれです");
        frame_.addWindowListener(new Test4_5WindowAdapter());

        frame_.setLayout(new BorderLayout());
        frame_.add(canvas_, BorderLayout.CENTER);
    }
    public void show() {
        frame_.pack();
        frame_.setVisible(true);
    }
}
/***************************************************************************/
class Test4_5Canvas extends Canvas {
    private String string_;
    public Test4_5Canvas(String string) {
        string_ = string;
    }
    public void paint(Graphics g) {
        // 描画対象のGraphicsオブジェクトにペンの色を設定します
        g.setColor(Color.green);
        // 文字列を指定座標に描画します.
        // Y座標は文字の上端ではなく「ベースライン」の座標です.
        // (概ね文字の下の方の座標ということです)
        g.drawString(string_, 20, 20);
    }
}
/***************************************************************************/
class Test4_5WindowAdapter extends WindowAdapter {
    public void windowClosing(WindowEvent ev) {
        System.exit(0);
    }
}

javac [ファイル名].java
でコンパイルして、
java Test4_5Main
で実行します.
実行するとタイトルバーしかないような小さなウィンドウが表示されると思います.
マウスを使ってウィンドウのサイズを広げてみましょう.ちゃんと文字列が表示されますね.

なぜウィンドウサイズが小さいのかは後回しにして、このプログラムで注目するのはCanvas(java.awt.Canvas)クラスを継承(extends)していることです.
Canvasとは言ってみれば「何もしないコンポーネント」です.

何もしないコンポーネントをフレームにaddしても何も起こらないのです.
従って、このコンポーネントは継承して使うことを前提にしています.

イベント処理のためだけに使うなどの理由で継承せずに使うことも可能です.

このプログラムではCanvasを継承して、paint(java.awt.Graphics)メソッドを定義しています.
paint(java.awt.Graphics)メソッドはCanvasクラスが継承しているComponentクラスに定義されているメソッドです.
このメソッドは「描画の必要があるときにシステムから自動的に呼び出される」ことになっています.
そして、Test4_5CanvasではComponentクラスに定義されているものと全く同じメソッド定義を行っているので、このメソッドをオーバーライドしたことになります.

実際に、Test4_5Canvasクラスのインスタンス(オブジェクト)に描画の必要が生じた場合(例えば他のウィンドウで隠された範囲が再び前面に現れるときなど)、Test4_5Canvasに定義されたpaintメソッドが呼び出されます.

ここで文字列の描画を行っているわけですが、paintメソッドのパラメタに渡されるGraphicsクラスのオブジェクトに対して、setColor、drawStringを呼び出しています.
Graphics(java.awt.Graphics)クラスとはどのようなものでしょう.

感覚的にわかりやすく言えば、これは、「描画対象と描画方法を内包したオブジェクト」と言ってみればいいんじゃないかと思います.
この例の場合は実際の描画対象はTest4_5Canvasクラスのオブジェクトなのですが、このクラスやその親にあたるCanvas、Componentクラスには自分に描画を行う機能はありません.
代わりにその描画対象オブジェクトに関連付けられたGraphicsオブジェクトが描画機能を受け持ちます.

なぜ、描画機能が別のクラスとして分けられているのでしょう.
実は答えは簡単で、描画対象はコンポーネントだけとは限らないからなのです.
例えば、プリンタに印字するときにはプリンタというデバイスに関連付けられたGraphicsオブジェクトに対して描画操作を行えば、画面に描画するのと同じようにプリンタの印刷イメージ上に描画できます.

さて、Graphicsクラスには、さまざまな描画メソッドの他に、自分自身の描画属性を持ちます.
ここでは、その一つである(描画時に使われる)前景色を設定してから、実際の描画を行っています.
g.setColor(Color.green);
このメソッドを呼び出すと、それ以降の描画メソッドによる描画の前景色が緑になります.
(これは実行結果を見れば明らかですね.)
そして、
g.drawString(string_, 20, 20);
によって、文字列をTest4_5Canvas上の(20, 20)という座標から描画しています.

Under Construction


5.Javaアプレット

やっとここまで来ましたか.
(ここまで説明をはしょったりやけに細かい所まで書いたりで、方針がばらばらでしたがそれはずばり疲れたからです..)

ではAppletにて文字列を表示するプログラムから行ってみましょう.

import java.awt.Graphics;
import java.applet.Applet;

public class Applet1_1 extends Applet {
    public void paint(Graphics g) {
        g.setColor(java.awt.Color.green);
        g.drawString("あぷれっとのてすとでえええす", 20, 20);
    }
}

たったこれだけです.
前のプログラム(Test4_5)から描画部分をもってきていますが、アプリケーション版に劣らない簡潔さといってよいと思います.

ただし、このプログラムをコンパイルした.classファイルだけではこのアプレットは実行できません.
アプリケーションの場合、.classファイルを直接実行するためにmain(String[])メソッドを定義しましたが、それがないと実行できないわけです.

public static void main(String[] args)
メソッドを定義しさえすれば、アプレットもアプリケーションとしてjava クラス名で実行できます.

アプレットはhtmlファイルに埋めこまれるプログラムなわけですから、それを埋めこむためのhtmlファイルが必要です.
ここでは以下のようなhtmlファイルを用意してテストしてみましょう.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
<HEAD>
</HEAD>
<BODY>
 <APPLET code="Applet1_1" width="400" height="200">
 </APPLET>
</BODY>
</HTML>

さて、このHTMLファイルをApplet1_1.classと同じディレクトリに置いておいて、ブラウザで開いてみましょう.
おそらくあっけなくアプレットが実行されて、表示が行われたと思います.
(失敗した方がいらっしゃった場合はご連絡ください.順次説明不足を補っていきたいと思います)

まずは簡単にHTMLファイルの方を説明しておきます.
一行めはテストでは無くても良いので無視します.って言うかappletタグだけでいいですよね...
appletタグには属性としてcode="クラス名"(クラスファイル名ではありません)と幅/高さを指定しておけばOKです.
この場合、htmlファイルとclassファイルが同じディレクトリに存在する必要があります.

package指定が無い場合です.
詳細はここでは割愛しますが、packageが宣言されているクラスの場合はそのパッケージ階層に等しいディレクトリ階層上にクラスファイルが存在する必要があります.

さあ、Appletの方の説明をしようと思いますが、これは、前のTest4_5Canvasとほとんど同じなんですよね.
違いは継承(extends)しているのがjava.applet.Appletクラスに変わっていることくらいです.
要はjava.awt.Appletクラスを継承したクラスはアプレットなのです.
というかアプレットとしても使用できるコンポーネントです.

アプレットの仕組み
java.applet.Appletクラスはjava.awt.Componentクラスを継承しているので、コンポーネントとして使用できます.
コンポーネントについては先程少し説明しましたが、ここでは、ボタンやテキストフィールドといった画面上に配置できる部品群のことです.
また、Webブラウザには、アプレットを配置するためのコンテナクラスが(見えないところに)装備されています.
Webブラウザはhtmlファイルに<applet>タグがあると、そこに書かれたApplet(を継承したクラス)を自分自身の内部に配置し、Appletクラスのinit()、start()、stop()、destroy()メソッドを適切なタイミングで呼び出す約束になっています.

要はAppletクラスを継承して、だいたい以下のようなメソッドを定義すればWebブラウザ上で動作するプログラムとなるわけですね.

public void init() そのページを最初に開こうとしたとき
public void start() そのページが表示されるとき(「戻る」ボタンなどの場合も)
public void stop() そのページが消去されるとき
public void destroy() そのページが破棄されるとき
public void paint(Graphics) アプレットの描画が必要になったとき
その他 その他に、Componentクラスの自動的に呼び出されるメソッド

尚、destroy()メソッドについては今のWebブラウザでは呼び出されるタイミングがちゃんと定まっていないかもしれません.(すみませんよく調べてません.アプレットはほとんど作ったことがないので...)
ページが破棄されるタイミングといえば、リロードを行ったときか、ブラウザを終了したときあたりになると思います.


6.Thread

6−1.基本

アプレットまではそのものの説明を省いていましたが、Threadは少し書いた方がよさそうです.
Threadとはプログラムの実行そのものを表します.直訳すれば「糸」ですね.
プログラムが実行を開始するとまず、一本のThreadが動き出します.
一昔前の普通のプログラムはこの線は一本しかありませんでした.
従って、並行的な処理を行わせたいときは、自分でタイムシェアリング的な処理を行ったりしたものでした.

例えば画面のどこかでキャラクタが簡単なアニメーションを行っているようなものを作ろうとしたとき、ひとコマ動かす処理(関数)を書いて、それをメインループから一定回数おきに呼び出させるとか、一定時間おきに発生する割り込みを使ってそこから呼び出させるとかしたものです.

マルチThread環境になり、プログラマが自由にThreadを生成出来るようになったことで、メインプログラムの実行と関係なくアニメーションを行わせたり、何かの状態を監視したりするプログラムがいとも簡単に書けるようになります.

Javaで平行実行を行いたいときは、java.lang.Threadクラスを継承したクラスを作ればよいです.

class TestThread extends Thread {
    public void run() {
        // 平行実行したい処理
          :
          :
    }
}

このようなクラスを作っておいて、new TestTread()としてThreaeオブジェクトを作って、そのstart()メソッドを呼び出すと、上記のrun()メソッドがstart()を呼び出したThreadと平行に実行されます.

作成したプログラムで上記のrun()メソッドを直接呼び出すと、これは単なるメソッド呼び出しなのでもちろん平行実行はされません.run()メソッドが復帰するまで呼び出した側は待たされます.
つまりThread#start()メソッドが新しい「実行の糸」を作った上でrun()を呼ぶという機能なわけですね.

Under Construction


7.これからの勉強方法

Javaのプログラムを作りたい!と思ったら、どんなプログラムを作っていきたいかによって勉強方法が変わってくると思います.
で、ある程度の分野に沿って勉強したい時に役立つURLと(手元にある)書籍を紹介したいと思います.


JavaおよびHotJavaは米国Sun Microsystems社の商標または登録商標です.