第16週目演習

下準備

テストドライバの導入

第16回目 (秋学期03回目) 分のテストドライバを導入する。以下の手順で行う。

  1. ダウンロード のページを開く (ここをクリック)
  2. プロジェクト「java2007」にある「test」の左側の「+」をクリック
  3. ツリーが展開されるので「install-libraries.xml」を右クリック
  4. 「実行(R)」にマウスカーソルを合わせる
  5. 「1 Ant ビルド」をクリック
  6. 「コンソール」タブに"BUILD SUCCESSFUL"と表示されれば成功
  7. eclipseの画面でプロジェクト「java2007」を右クリック
  8. メニューが表示されるので、「更新」をクリック
  9. "week16.zip" をデスクトップなどにダウンロード
  10. eclipseの画面でプロジェクト「java2007」を右クリック
  11. メニューから「インポート(I)」を選択
  12. 「インポート」ウィンドウが表示されるので、「Zip ファイル」を選択
  13. 「次へ(N)」をクリック
  14. 宛先フォルダー(L): が「java2007」になっていることを確認
  15. From archive file: の右側にある 「参照(R)...」をクリック
  16. ファイルダイアログが表示されるので、ダイアログ内に表示されたダウンロードしたファイルをダブルクリック
  17. 前の画面に戻るので、From archive file: のエリアに正しいパスが入力されていることを確認
  18. フォルダ「/」の左にチェックがついていることを確認 (ついていなければチェックボックスをクリック)
  19. 「警告を出さずに既存リソースを上書き」にチェックがついていることを確認 (上書きしたくないファイルがある場合はチェックを外す)
  20. 「終了 (F)」をクリック

第16週目テストドライバの導入に成功すると、java2007 プロジェクトの test フォルダに j2.lesson03.xml というファイルが作成される。

パッケージの作成

過去の演習を参考にして、「j2.lesson03」というパッケージを作成する。

時計を表すクラス

24時時計を表すクラス Clock を作成する。

このクラスには、次の公開要素を用意する。

インスタンスフィールドの抽出

時計が持つ属性を考える。

int 型の time というインスタンスフィールドを用意し、このフィールドが不正に参照されないように private を指定する。

この time というインスタンスフィールドは、現在時刻を0時0分0秒からの経過した秒数で表すことにする。カプセル化という手法を用いると、実際に持つデータの形式 (内部表現ともいう) をカプセルの中に隠蔽してしまって、外部からは内部表現を意識しなくてもよいことになる。

コンストラクタの抽出

次のようなコンストラクタを用意する。

public Clock(int hour, int minute, int second)
時、分、秒を指定して時計インスタンスを作成する。

これもカプセル化の恩恵で、内部表現は「0時0分0秒からの経過した秒数」であるが、外部からこのプログラムを使う際には慣れ親しんだ「時、分、秒」といった単位を使ってプログラムを作成できる。

インスタンスメソッドの抽出

2つのメソッドを用意する。

public void tick(int n)
n 秒だけ時計を進める
public void show()
現在の時刻を "h時m分s秒" の形式でコンソールに表示する

どちらも時計という物体そのものに対する操作をする (時計をn秒だけ進める, 時計の時刻を表示する) ので、インスタンスメソッドとして定義する。

骨格の作成

クラスの作成

以下の手順で、パッケージ「j2.lesson03」に「Clock」クラスを作成する。

  1. 先ほど作成したパッケージ 「j2.lesson03」の上で右クリック
  2. マウスカーソルを「新規」に合わせる
  3. 「クラス」をクリック
  4. クラス名は Clock とする

インスタンスフィールドの作成

Clock クラス内にインスタンスフィールドを作成する。

package j2.lesson03;

public class Clock {
    
    // 時刻の内部表現
    private int time;

}

コンストラクタの作成

同様に、コンストラクタを用意する。

コンストラクタは戻り値を取らないため、ダミーの return 文を用意する必要はない。

package j2.lesson03;

public class Clock {
    
    // 時刻の内部表現
    private int time;
    
    // 時刻を設定するコンストラクタ
    public Clock(int hour, int minute, int second) {
    }
}

インスタンスメソッドの作成

最後に、インスタンスメソッドを用意する。

どちらも戻り値が void 型なので、ダミーの return 文は必要ない。

package j2.lesson03;

public class Clock {
    
    // 時刻の内部表現
    private int time;
    
    // 時刻を設定するコンストラクタ
    public Clock(int hour, int minute, int second) {
    }
    
    // 時刻を n 秒進める
    public void tick(int n) {
    }
    
    // 時刻を表示する
    public void show() {
    }
}

骨格テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

「Clockに対する骨格テスト」 を実行する。

骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(クラス名), existence j2.lesson03 に対象のクラスが存在していない。パッケージやクラス名を確認
(メンバ名), existence 対象の名前を持つメンバが存在していない
(メンバ名), public 対象のメンバが public で宣言されていない
(メンバ名), private 対象のメンバが private で宣言されていない
(メソッド名), not static インスタンスメソッドを作る際に static を指定している
(メソッド名), type <T> メソッドを作る際に戻り値の型を間違えている (正しくは <T>)

各種の実装

今回も擬似コードを書くまでもないので、直接プログラムを書く。

コンストラクタの実装

コンストラクタは、渡された引数を「0時0分0秒からの経過した秒数」という形式に変換する必要がある。そのため、インスタンスフィールド time に現在時刻を代入する前に、いくらかの計算を行う。

// 時刻を設定するコンストラクタ
public Clock(int hour, int minute, int second) {
    this.time = hour * 3600 + minute * 60 + second;
}

tick メソッドの実装

tick メソッドでは、与えられた n の値だけ時計を進める。ここで、n も time も単位が「秒」であるため、余計な変換をする必要はない。

ただし、一日は 24 * 60 * 60 = 86400 秒であるので、time の値は 0 以上 86400 未満に収められるように調整する。これは、86400 で割った値のあまりを使えばよい。

// 時刻を n 秒進める
public void tick(int n) {
    this.time += n;
    this.time %= 86400;
}

show メソッドの実装

show メソッドでは、このインスタンスが持つ現在時刻を表示する。時分秒の形式で表すが、内部表現 (time) が秒なのでそのような形式に変換してから表示する。

// 時刻を表示する
public void show() {
    // 現在時刻の 時, 分, 秒 を  h, m, s で表すとして 
    // print h + "時" + m + "分" + s + "秒", 改行
    int h = this.time / 3600;
    int m = (this.time / 60) % 60;
    int s = this.time % 60;
    System.out.println(h + "時" + m + "分" + s + "秒");
}

単体テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

Clock クラス内に mainメソッドを作成し、その中に簡単なテストを記述してみる。例えば、次のような形でよい。

public static void main(String[] args) {
    Clock c = new Clock(3, 2, 1);
    c.show();
    c.tick(1);
    c.show();
    c.tick(60 * 2);
    c.show();
    c.tick(3600 * 3);
    c.show();
    c.tick(86400 - 3600 - 60 - 1);
    c.show();
}

このプログラムを実行し、各メソッドが期待通りに動いていることを確認する。確認したら、mainメソッドを消しても良い。

「Clockに対する単体テスト」 を実行する。

単体テストを行った際に緑のバーが表示されれば、各メソッドはある程度正しく作成されていると思われる。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(メソッド名), 期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。下記のテスト項目を参照

テスト項目は以下の通りである。

項目名 詳細
Ctor_000 new Clock(0, 0, 0).show()
Ctor_123 new Clock(1, 2, 3).show()
tick_000001 new Clock(0, 0, 0).tick(1).show()
tick_000030 new Clock(0, 0, 0).tick(30).show()
tick_000059 new Clock(0, 0, 0).tick(59).show()
tick_000100 new Clock(0, 0, 0).tick(60).show()
tick_003000 new Clock(0, 0, 0).tick(1800).show()
tick_005959 new Clock(0, 0, 0).tick(3599).show()
tick_010000 new Clock(0, 0, 0).tick(3600).show()
tick_120000 new Clock(0, 0, 0).tick(43200).show()
tick_235959 new Clock(0, 0, 0).tick(86399).show()
tick_loop new Clock(23, 59, 59).tick(1).show()

プログラム全体

Clockクラス全体を掲載しておく。

package j2.lesson03;

public class Clock {
    
    // 時刻の内部表現
    private int time;
    
    // 時刻を設定するコンストラクタ
    public Clock(int hour, int minute, int second) {
        this.time = hour * 3600 + minute * 60 + second;
    }
    
    // 時刻を n 秒進める
    public void tick(int n) {
        this.time += n;
        this.time %= 86400;
    }
    
    // 時刻を表示する
    public void show() {
        // 現在時刻の 時, 分, 秒 を  h, m, s で表すとして 
        // print h + "時" + m + "分" + s + "秒", 改行
        int h = this.time / 3600;
        int m = (this.time / 60) % 60;
        int s = this.time % 60;
        System.out.println(h + "時" + m + "分" + s + "秒");
    }
}

Clock クラスを使うプログラム

カプセル化されたクラスを使用する練習として、Clock クラスを使用するプログラムを考える。

擬似コードの作成

「プログラム全体」の擬似コード

プログラム全体の擬似コードは以下のようにする。

プログラム全体
    0時0分0秒の時計を作る
    無限ループ
        print 進める秒数を入力 (0で終了):
        input = コンソール入力 (int)
        input == 0 ならば終了
        clock を input だけ進める
        clock の内容を見る

骨格の作成

クラスの作成

以下の手順で、パッケージ「j2.lesson03」に「ClockAction」クラスを作成する。

  1. 先ほど作成したパッケージ 「j2.lesson03」の上で右クリック
  2. マウスカーソルを「新規」に合わせる
  3. 「クラス」をクリック
  4. クラス名は ClockAction とする

擬似コードの貼り付け (骨格のみ)

作成したクラスに、各擬似コードの名前を貼り付ける。ただし、「プログラム全体」ではコンソールからの入力を行っていたため、import java.io.*; も追加する。

package j2.lesson03;

import java.io.*;

public class ClockAction {
    // プログラム全体
}

mainメソッドの作成 (骨格のみ)

擬似コード「プログラム全体」に合わせて、クラス「ClockAction」内にpublic static void main(String[] args) から始まるメソッドを作成する。ただし、擬似コード「プログラム全体」ではコンソールからの入力を行っていたので、throws IOExceptionをつける。

全体の骨格

ここまでのプログラムの骨格は以下のようになる。

package j2.lesson03;

import java.io.*;

public class ClockAction {

    // プログラム全体
    public static void main(String[] args) throws IOException {
    }
}

骨格テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

「ClockActionに対する骨格テスト」 を実行する。

骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(クラス名), existence j2.lesson03 に対象のクラスが存在していない。パッケージやクラス名を確認
(メソッド名), existence 指定されたメソッドが存在しない
(メソッド名), public メソッドを作る際に public が抜けている
(メソッド名), static メソッドを作る際に static が抜けている
(メソッド名), type <T> メソッドを作る際に戻り値の型を間違えている (正しくは <T>)
(メソッド名), throws java.io.IOException メソッドを作る際に throws IOException が抜けている

アクセス制御の確認

プログラムとは関係ないが、アクセス修飾子によって非公開要素を参照できないことを確認する。

ClockActionクラスのmainメソッドに次のようなプログラムを書き (コピーアンドペーストでかまわない)、どのようなことが起こるか確認せよ。

package j2.lesson03;

import java.io.*;

// 前週のPointクラスをインポート
import j2.lesson02.Point;

public class ClockAction {

    // プログラム全体
    public static void main(String[] args) throws IOException {
        // 他のパッケージ内のクラス
        Point p1 = new Point(0.0, 0.0);
        Point p2 = new Point(1.0, 1.0);
        System.out.println("(" + p1.x + ", " + p1.y + ")");
        System.out.println(p1.distance(p2));

        // private フィールドを参照
        Clock c = new Clock(9, 30, 0);
        System.out.println(c.time);
    }
}

このプログラムはコンパイルできないため、確認したらmainの中身を空にしてかまわない。

プログラムへの変換

main メソッドの実装

続けて、先ほど作成した ClockAction クラスの main メソッドの中身を記述する。

まずは擬似コードをコメントとして貼り付ける。

// プログラム全体
public static void main(String[] args) throws IOException {
    // 0時0分0秒の時計を作る
    // 無限ループ
        // print 進める秒数を入力 (0で終了):
        // input = コンソール入力 (int)
        // input == 0 ならば終了
        // clock を input だけ進める
        // clock の内容を見る
}

これを元にプログラムを作成する。

// プログラム全体
public static void main(String[] args) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    
    // 0時0分0秒の時計を作る
    Clock clock = new Clock(0, 0, 0);
    
    // 無限ループ
    while(true) {
        // print 進める秒数を入力 (0で終了):
        System.out.print("進める秒数を入力 (0で終了):");
        
        // input = コンソール入力 (int)
        int input = Integer.parseInt(reader.readLine());
        
        // input == 0 ならば終了
        if (input == 0) {
            return;
        }
        
        // clock を input だけ進める
        clock.tick(input);
        
        // clock の内容を見る
        clock.show();
    }
}

全体的には下記のようなプログラムにする。

package j2.lesson03;

import java.io.*;

public class ClockAction {

    // プログラム全体
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        
        // 0時0分0秒の時計を作る
        Clock clock = new Clock(0, 0, 0);
        
        // 無限ループ
        while(true) {
            // print 進める秒数を入力 (0で終了):
            System.out.print("進める秒数を入力 (0で終了):");
            
            // input = コンソール入力 (int)
            int input = Integer.parseInt(reader.readLine());
            
            // input == 0 ならば終了
            if (input == 0) {
                return;
            }
            
            // clock を input だけ進める
            clock.tick(input);
            
            // clock の内容を見る
            clock.show();
        }
    }
}

プログラムの実行

前回と同じ要領でプログラムを実行する。

プログラムの実行に成功し、順に 10, 100, 10000, 70000, 6289, 1, 0 と入力すると以下のように表示される。

進める秒数を入力 (0で終了):10
0時0分10秒
進める秒数を入力 (0で終了):100
0時1分50秒
進める秒数を入力 (0で終了):10000
2時48分30秒
進める秒数を入力 (0で終了):70000
22時15分10秒
進める秒数を入力 (0で終了):6289
23時59分59秒
進める秒数を入力 (0で終了):1
0時0分0秒
進める秒数を入力 (0で終了):0

機能テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

「ClockActionに対する機能テスト」 を実行する。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
期待された結果と異なります 出力された結果が期待された値と異なる。

機能テストの項目

項目名 テストの内容
sample サンプルと同じ入力でプログラムを実行