メソッド method とクラス class

次のプログラムを考えてみましょう。

import java.io.*;
class Test {
    public static void main(String args[]) throws IOException {
        DataInputStream d = new DataInputStream(System.in);
                                      // データ入力の準備
        System.out.print("あなたは何歳ですか? ");
        System.out.flush();           // 強制出力
        String s = d.readLine();      // 文字列の入力
        int a = Integer.parseInt(s);  // 整数に変換
        System.out.print("初恋は何歳でしたか? ");
        System.out.flush();           // 強制出力
        String s = d.readLine();      // 文字列の入力
        int b = Integer.parseInt(s);  // 整数に変換
        System.out.println("あれから " + (a - b) + " 年たちましたね");
    }
}

これは完璧なプログラムですが, メッセージを出力して整数を入力する部分が繰り返し現れています。 同じようなところは,まとめて「メソッド」として定義するのが賢い方法です。 メソッドを使ってまとめると次のようになります。

import java.io.*;
class Test {
    static int getInt(String s) throws IOException {
        DataInputStream d = new DataInputStream(System.in);
                                      // データ入力の準備
        System.out.print(s);          // メッセージ出力
        System.out.flush();           // 強制出力
        String t = d.readLine();      // 文字列の入力
        return Integer.parseInt(t);   // 整数に変換して返す
    }
    public static void main(String args[]) throws IOException {
        int a = getInt("あなたは何歳ですか? ");
        int b = getInt("初恋は何歳でしたか? ");
        System.out.println("あれから " + (a - b) + " 年たちましたね");
    }
}

メッセージを出力し,整数を入力する部分を,getInt と いうメソッドにまとめました。

じつは main も一つのメソッドです。 上のプログラムは,main と getInt の二つのメソッドを 含むことになります。

メソッド getInt の頭に付いている static は, main にも付いている語ですね。この意味は,後で詳しく述べます。

その後の int は,getInt が int 型の値 を返すことを意味します。 これに対して,main メソッドに付いている void という語は, main メソッドが何も値を返さないことを意味します。

getInt(String s) の String s は, この getInt というメソッドが, 文字列(ストリング)を受け取ることを意味します。 受け取った文字列は s という変数に代入されます。

この文字列 s のようにメソッドが受け取るもののことを, メソッドの引数(ひきすう)といいます。

メソッドを使うことを,メソッドを「呼び出す」ともいいます。 ここでは

        int a = getInt("あなたは何歳ですか? ");
のように,"あなたは何歳ですか? " という文字列を 引数としてメソッド getInt() を呼び出しています。 このメソッドは,キーボードからの入力を整数に直して, その値を返します。 メソッドが返す値のことを戻り値(もどりち)などといいます。

この getInt() メソッドの中では, キーボードから入力する部分がありますが, ここでは入出力の例外(I/O Exception)が生じる可能性があります。 例外とは,いわゆるエラーのことです。 エラーが生じたときは,自前でエラー処理することもできますが, エラー処理を他に任せてしまうこともできます。 このための宣言が throws IOException でした。

throws IOException は,このメソッドの中で入出力エラーが 生じたなら,それをそのまま呼び出し側に投げる(throw する)ことを 意味します。つまり,main メソッドから getInt メソッド を呼び出したとき, getInt メソッド内で生じた入出力エラーは main メソッド にそのまま投げられるので,main メソッドもまた 入出力エラーを投げる必要があります。最後に入出力エラーを受け取って エラーメッセージを発するのは,Javaインタープリタです。

エラーを投げないで,メソッド内で自前で処理してしまうことも可能です。 それには,次のようにします。

import java.io.*;
class Test {
    static int getInt(String s) {
        DataInputStream d = new DataInputStream(System.in);
                                      // データ入力の準備
        System.out.print(s);          // メッセージ出力
        System.out.flush();           // 強制出力
        try {                         // トライする
            return Integer.parseInt(d.readLine());
        } catch (IOException e) {     // 入出力の例外なら
            return 0;                 // 0 を返す
        }
    }
    public static void main(String args[]) {
        int a = getInt("あなたは何歳ですか? ");
        int b = getInt("初恋は何歳でしたか? ");
        System.out.println("あれから " + (a - b) + " 年たちましたね");
    }
}

readLine() のような入出力メソッドを用いるときは, 必ず throws か catch を使わないと, コンパイラに叱られます。

throws でエラーを他に転嫁せず, catch でエラーを捕まえれば,エラーの連鎖は断ち切れ, 他でエラー処理する必要はなくなります。

実際には入出力エラーより, Integer.parseInt() メソッドで 文字列が整数に変換できないというエラーが 起きることのほうが多いと思います。 入出力エラーのように実際に入力装置にエラーが発生したときのエラーは 絶対に throws か catch で処理しなければなりません。 これに対して,文字列が整数に直せないといったような, いわゆる「実行時のエラー」は, throws や catch で処理する必要はありませんが, 処理してもかまいません。 処理するには次のようにします。

        try {                         // トライする
            return Integer.parseInt(d.readLine());
        } catch (IOException e) {     // 入出力の例外なら
            return 0;                 // 0 を返す
        } catch (NumberFormatException e) {  // 数値形式の例外なら
            return 0;                 // 0 を返す
        }
これで,数の形式が変なときも 0 を返すようになります。

クラスを独立させる

さきほどの数値入力の例で,今度はメソッドだけでなく, クラスを独立させてしまいましょう。

それから,ついでですので整数入力ではなく, より一般性のある実数(double 型)の入力にしてみました。

import java.io.*;

class GetNumber {
    DataInputStream d;

    GetNumber() {  // GetNumber クラスを初期化するメソッド
        d = new DataInputStream(System.in); // データ入力の準備
    }

    double get(String s) {            // get というメソッドを作る
        System.out.print(s);          // メッセージ出力
        System.out.flush();           // 強制出力
        try {                         // トライする
            return Double.valueOf(d.readLine()).doubleValue();
        } catch (IOException e) {     // 入出力の例外なら
            return Double.NaN;        // NaN を返す
        } catch (NumberFormatException e) {  // 数値形式の例外なら
            return Double.NaN;        // NaN を返す
        }
    }
}

class Test {
    public static void main(String args[]) {
        GetNumber g = new GetNumber();  // GetNumber クラスを使う
        double a = g.get("あなたは何歳ですか? ");
        double b = g.get("初恋は何歳でしたか? ");
        System.out.println("あれから " + (a - b) + " 年たちましたね");
    }
}

これ全体を Test.java というファイルに書き込んで, コンパイルすると,Test.class だけでなく, GetNumber.class というクラスファイルも カレントディレクトリにできます。実行するには,以前と同様に,

    java Test
とします。

このプログラムの get() メソッドは, エラーが起こったときに Double.NaN という変な値を返しています。 この NaN は Not-a-Number(数でないもの)の意味で, double 型や float 型に用意されている特別の値です。

もし double 型でなく int 型が欲しい場合は, 次のようにして,(int) を付けて型変換します。

class Test {
    public static void main(String args[]) {
        GetNumber g = new GetNumber();  // GetNumber クラスを使う
        int a = (int) g.get("あなたは何歳ですか? ");  // 型変換
        int b = (int) g.get("初恋は何歳でしたか? ");  // 型変換
        System.out.println("あれから " + (a - b) + " 年たちましたね");
    }
}

この (int) のように, 型の名前をかっこに入れて付けて型を変換することを, キャストする(cast)といいます。