第17週目演習

下準備

テストドライバの導入

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

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

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

パッケージの作成

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

学生を表すクラス Student の作成

学生を表すクラス Student を j2.lesson04 パッケージ内に作成する。このクラスは、演習の中で親クラスとして利用されるため、以下の内容で必ず作成すること。

package j2.lesson04;

// 学生を表すクラス
public class Student {
    
    // 名前 (通常は変化しないので final)
    private final String name;
    
    // 学籍番号 (通常は変化しないので final)
    private final String id;
    
    // 名前と学籍番号を設定するコンストラクタ
    public Student(String name, String id) {
        this.name = name;
        this.id = id;
    }
    
    // 名前を取得する
    public String getName() {
        return this.name;
    }
    
    // 学籍番号を取得する
    public String getId() {
        return this.id;
    }
}

クラスの動きを予想できたら、上記をコピーして対象のソースファイルにペーストすればよい。

受講者を表すクラス

ある授業の受講者を表すクラス CourseStudent を作成する。

受講者を表すインスタンスは、次のような特徴を持つことにする。

親クラスの抽出

受講者 is-a 学生 より、Student クラスを継承する。

これにより、次のメンバが CourseStudent クラスに引き継がれる。

また、コンストラクタが「public Student(String name, String id)」であるため、CourseStudent クラスのコンストラクタから super によって呼び出す必要がある。

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

受講生を表すインスタンスは、次のような特徴があった。

そのため、次のようなデータが必要になる

しかし、これら全てをインスタンスフィールドとして定義する必要はない。

上記より、int 型の score というインスタンスフィールドを用意し、このフィールドが不正に参照されないように private を指定する。また、得点を書き換える機能を持たないため、final も指定する。

まとめると、次の特徴からインスタンスフィールドを抽出した。

コンストラクタの抽出

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

インスタンスフィールドの初期化に必要な情報は得点のみであったが、親クラスのインスタンスフィールドの初期化に名前と学籍番号が必要である。そのため、上記のようなコンストラクタになる。

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

受講者を表すインスタンスの特徴より、インスタンスメソッドを抽出する。

上記より、インスタンスメソッドの仕様を決定する。

骨格の作成

クラスの作成

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

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

親クラスの設定

CourseStudent に Student クラスを継承させる。

クラス名の後に extends Student を付け加えればよい。

package j2.lesson04;

// 受講者を表すクラス
public class CourseStudent extends Student {
    
}

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

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

package j2.lesson04;

// 受講者を表すクラス
public class CourseStudent extends Student {
    
    // 得点
    private final int score;
}

コンストラクタの作成

同様に、コンストラクタを用意する。Student クラスのコンストラクタが「Student(String name, String id)」であるため、super(name, id); によって親クラスに名前と学籍番号の情報を与える。

また、 score を final で宣言したため、コンストラクタ内で初期化する。

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

package j2.lesson04;

// 受講者を表すクラス
public class CourseStudent extends Student {
    
    // 得点
    private final int score;
    
    // 受講者を表すインスタンスを作成する
    public CourseStudent(String name, String id, int score) {
        super(name, id);
        this.score = score;
    }
}

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

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

ダミーの return 文を追加する。String 型を返すメソッドでは、空の文字列 "" を return するか、「無意味なもの」を表す null を返せばよい。ここでは空の文字列を返しておく。

package j2.lesson04;

// 受講者を表すクラス
public class CourseStudent extends Student {
    
    // 得点
    private final int score;
    
    // 受講者を表すインスタンスを作成する
    public CourseStudent(String name, String id, int score) {
        super(name, id);
        this.score = score;
    }

    // 得点を取得する
    public int getScore() {
        return 0;
    }
    
    // 成績情報を文字列で返す
    // 学籍番号 + "," + 名前 + "," + rank
    public String getPerformance() {
        return "";
    }
}

骨格テスト

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

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

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

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

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

各種の実装

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

コンストラクタの実装

コンストラクタでは、CourseStudent のインスタンスフィールドを設定する。name, id は Student インスタンスのフィールドなので、ここでは score のみ設定する。つまり、骨格と同様である。

    // 受講者を表すインスタンスを作成する
    public CourseStudent(String name, String id, int score) {
        super(name, id);
        this.score = score;
    }

getScore メソッドの実装

getScore メソッドでは、単に インスタンスフィールドの score を返すだけである。インスタンスフィールドは外部から不正に変更されたくないため public で宣言し、getScore() というメソッドを用意して値を取得している。

// 得点を取得する
public int getScore() {
    return this.score;
}

このように、フィールドの値を取得するためのメソッドを一般的にgetterと呼び、「get + フィールド名」の名前を持たせる。

getPerformance メソッドの実装

getPerformance メソッドでは、成績情報を「学籍番号 + "," + 名前 + "," + rank」の形式の文字列で返す。それぞれの情報は次のように用意すればよい。

// 成績情報を文字列で返す
// 学籍番号 + "," + 名前 + "," + rank
public String getPerformance() {
    // score =   ~59 : D
    // score = 60~69 : C
    // score = 70~79 : B
    // score = 80~   : A
    String rank;
    if (this.score <= 59) {
        rank = "D";
    }
    else if (this.score <= 69) {
        rank = "C";
    }
    else if (this.score <= 79) {
        rank = "B";
    }
    else {
        rank = "A";
    }
    
    // 学籍番号 + "," + 名前 + "," + rank
    // this を省略して そのまま get~() でもよい。
    return this.getId() + "," + this.getName() + "," + rank;
}

単体テスト

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

余裕があれば、JUnitを用いて単体テストを記述せよ。

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

// テストプログラム
public static void main(String[] args) {
    CourseStudent student = new CourseStudent("ほげ", "n99k999", 100);
    System.out.println(student.getName());
    System.out.println(student.getId());
    System.out.println(student.getScore());
    System.out.println(student.getPerformance());
}

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

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

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

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

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

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

項目名 詳細
getPerformance_a new CourseStudent("a", "b", 0).getPerformance()
getPerformance_b new CourseStudent("c", "d", 30).getPerformance()
getPerformance_c new CourseStudent("e", "f", 59).getPerformance()
getPerformance_d new CourseStudent("g", "h", 60).getPerformance()
getPerformance_e new CourseStudent("i", "j", 65).getPerformance()
getPerformance_f new CourseStudent("k", "l", 69).getPerformance()
getPerformance_g new CourseStudent("m", "n", 70).getPerformance()
getPerformance_h new CourseStudent("o", "p", 75).getPerformance()
getPerformance_i new CourseStudent("q", "r", 79).getPerformance()
getPerformance_j new CourseStudent("s", "t", 80).getPerformance()
getPerformance_k new CourseStudent("u", "v", 90).getPerformance()
getPerformance_perfect new CourseStudent("ほげ", "n99k9999", 100).getPerformance()

プログラム全体

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

package j2.lesson04;

// 受講者を表すクラス
public class CourseStudent extends Student {
    
    // 得点
    private final int score;
    
    // 受講者を表すインスタンスを作成する
    public CourseStudent(String name, String id, int score) {
        super(name, id);
        this.score = score;
    }

    // 得点を取得する
    public int getScore() {
        return this.score;
    }
    
    // 成績情報を文字列で返す
    // 学籍番号 + "," + 名前 + "," + rank
    public String getPerformance() {
        // score =   ~59 : D
        // score = 60~69 : C
        // score = 70~79 : B
        // score = 80~   : A
        String rank;
        if (this.score <= 59) {
            rank = "D";
        }
        else if (this.score <= 69) {
            rank = "C";
        }
        else if (this.score <= 79) {
            rank = "B";
        }
        else {
            rank = "A";
        }
        
        // 学籍番号 + "," + 名前 + "," + rank
        // this を省略して そのまま get~() でもよい。
        return this.getId() + "," + this.getName() + "," + rank;
    }
}

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

CourseStudent クラスを使用するプログラムを考える。

擬似コードの作成

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

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

全体の処理
    print "名前を入力:"
    name = コンソール入力 (String)
    print "学籍番号を入力:"
    id = コンソール入力 (String)
    print "得点を入力:"
    score = コンソール入力 (int)
    student = CourceStudent 型の新しいインスタンス
              ただし、name, id, score で初期化
    print student.getPerformance()

骨格の作成

クラスの作成

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

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

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

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

package j2.lesson04;

import java.io.*;

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

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

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

全体の骨格

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

package j2.lesson04;

import java.io.*;

public class CourseStudentAction {

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

骨格テスト

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

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

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

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

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

継承の確認

プログラムとは関係ないが、継承によって親クラスで定義したインスタンスメソッドを、子クラスのインスタンスから使用できることを確認する。

また、拡大変換が可能であることを確認する。

CourseStudentActionクラスのmainメソッドに次のようなプログラムを書き、どのようなことが起こるか確認せよ。

package j2.lesson04;

import java.io.*;

public class CourseStudentAction {

    // 全体の処理
    public static void main(String[] args) throws IOException {
        CourseStudent cs = new CourseStudent("hoge", "m00k0000");
        // 親クラスからの継承確認
        System.out.println(cs.getName());
        System.out.println(cs.getId());

        // 拡大変換
        Student s = new CourseStudent("foo", "n99k9999");
        System.out.println(s.getName());
        System.out.println(s.getId());
        // これはできない (Student に getScore メソッドがない)
        // System.out.println(s.getScore());
    }
}

確認したらmainの中身を空にしてかまわない。

プログラムへの変換

main メソッドの実装

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

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

// 全体の処理
public static void main(String[] args) throws IOException {
    // print "名前を入力:"
    // name = コンソール入力 (String)
    // print "学籍番号を入力:"
    // id = コンソール入力 (String)
    // print "得点を入力:"
    // score = コンソール入力 (int)
    // student = CourceStudent 型の新しいインスタンス
    //           ただし、name, id, score で初期化
    // print student.getPerformance()
}

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

// 全体の処理
public static void main(String[] args) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    
    // print "名前を入力:"
    System.out.print("名前を入力:");
    // name = コンソール入力 (String)
    String name = reader.readLine();
    
    // print "学籍番号を入力:"
    System.out.print("学籍番号を入力:");
    // id = コンソール入力 (String)
    String id = reader.readLine();
    
    // print "得点を入力:"
    System.out.print("得点を入力:");
    // score = コンソール入力 (int)
    int score = Integer.parseInt(reader.readLine());
    
    // student = CourceStudent 型の新しいインスタンス
    //           ただし、name, id, score で初期化
    CourseStudent student = new CourseStudent(name, id, score);
    
    // print student.getPerformance()
    System.out.println(student.getPerformance());
}

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

package j2.lesson04;

import java.io.*;

public class CourseStudentAction {

    // 全体の処理
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        
        // print "名前を入力:"
        System.out.print("名前を入力:");
        // name = コンソール入力 (String)
        String name = reader.readLine();
        
        // print "学籍番号を入力:"
        System.out.print("学籍番号を入力:");
        // id = コンソール入力 (String)
        String id = reader.readLine();
        
        // print "得点を入力:"
        System.out.print("得点を入力:");
        // score = コンソール入力 (int)
        int score = Integer.parseInt(reader.readLine());
        
        // student = CourceStudent 型の新しいインスタンス
        //           ただし、name, id, score で初期化
        CourseStudent student = new CourseStudent(name, id, score);
        
        // print student.getPerformance()
        System.out.println(student.getPerformance());
    }
}

プログラムの実行

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

プログラムの実行に成功し、順に ほげ, n99k9999, 78 と入力すると以下のように表示される。

名前を入力:ほげ
学籍番号を入力:n99k9999
得点を入力:78
n99k9999,ほげ,B

機能テスト

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

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

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

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

機能テストの項目

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