コンピュータ基礎および演習II

講義資料

メソッド

今回のテーマは「メソッド (method) 」です。 Java のメソッドは、プログラムの一部を抜き出し、 ひとまとめにして名前をつけたものです。 その名前を指定することによって、 メソッドの中身として書いてある処理を行うように指示することができます。 これをメソッドの起動 (invoke) 、あるいはメソッドの実行と言います。

(Java のメソッドは C の関数と似た概念のため、 「関数呼び出し (function call) 」という言い方にちなんで、 「メソッドの呼び出し」と言うこともあります。)

メソッドを用いたプログラムの例

Java のメソッドは、数学の関数と対比して考えると理解の助けになるはずです。 次の数式を考えてみましょう。

y = f(x)

数学の世界では、x から y への写像 f を関数と呼びましたね。

Java のプログラムではこの式は、 変数 x の値をメソッド f に渡し、 所定の計算を行った結果の値を変数 y に代入する、という意味になります。 メソッド f の中身に行うべき計算の方法を書いておきます。

n 個の中から m 個を選ぶ組み合わせの数 nm の計算を考えます。 nm は次のような式で求まります。

nm = n! / ( m! * (n - m)! )

これをプログラムとして書くと次のようになります。

// n! の計算
nx = 1;
for (i = 1; i <= n; i = i + 1)
    nx = nx * i;

// m! の計算
mx = 1;
for (i = 1; i <= m; i = i + 1)
    mx = mx * i;

// (n-m)! の計算
nmx = 1;
for (i = 1; i <= n - m; i = i + 1)
    nmx = nmx * i;

// n_C_m の計算
combination = nx / (mx * nmx);

このプログラムでは階乗の計算が 3 回現れます。 3 回とも計算のやり方は同じですが、どの値の階乗を求めるのか、 また計算結果をどの変数に入れるのかが異なるため、 ほとんど同じ形のプログラムを 3 回書かなければならないのです。

このような場合、階乗を計算する仕事をメソッドとして、 ひとまとめにしてしまえば良いのです。 階乗を求めるメソッドに fact という名前をつけたとすると、 上のプログラムは、次のように簡単なものになります。

combination = fact(n) / (fact(m) * fact(n - m));

n, m, n-m などの、メソッドに与えるパラメータ (この例では階乗を求める数) のことを引数 (ひきすう) と呼びます。

メソッド fact の定義は、次のように書きます。

static int fact(int x) {
    int f = 1;
    for (int i = 1; i <= x; i++)
        f = f * i;
    return f;
}

このメソッドの引数は x です。 メソッドが実行されたとき、 x には、メソッドの実行元から渡された引数の値が入っています。 メソッド内では x の値をもとにして階乗を計算し、 return 文で実行元に返しています。

詳しい文法は後回しにして、 メソッドを用いるとどんなメリットがあるかをまとめておきます。

例題では、メソッドを使うことによって、 同じプログラムを複数回書くという手間を省くことができました。 計算方法に名前をつけて定義しておけば、それを何回でも実行できます。

また、大きなプログラムをひとまとめに書くと理解するのが大変です。 メソッドによって大きな仕事を部分に分けて、 それぞれを別々に理解することによって、 プログラムを分かりやすくすることができます。

メソッドの宣言

実は、今までのプログラムに必ず現れた main もメソッドの一つです。 一般にプログラムは、次のようにいくつかのメソッドを並べた形をしています。 メソッド一つ一つの宣言は、 main と同列に並べて書けば良いのです。

class プログラムの名前 {
    public static void main(String[] args) {
        ....
        main メソッド
        プログラムは main メソッドから実行が開始される
        ....
    }

    メソッドの宣言1
    ....
    メソッドの宣言2
    ....
    ....
    メソッドの宣言n
}

そして、「メソッドの宣言」の形式は次のとおりです。

    static  結果の型  メソッド名  ( 引数の宣言 )  {
        .... 
        メソッドの本体
        .... 
    }

Java はオブジェクト指向プログラミング言語と呼ばれていますが、 まだオブジェクトのことは学んでいません。 static はオブジェクトを使わないプログラムにおいて、 メソッドを使うためのキーワードです。 今のところ「おまじない」としておいて構いません。 逆に、次回以降に学ぶオブジェクトを使ったプログラムでは、 基本的に static をつけてはいけません。詳しくは次回に。

「結果の型」は、メソッドがどんな型の値を返すのかを設定します。 整数を返すのであれば int 、 浮動小数点数を返すのであれば double と書きます。

「引数の宣言」の部分には、引数の型と変数名を並べて書きます。 書き方は普通の変数宣言とほぼ同様ですが、 セミコロンは書きません。引数が複数個ある場合は、コンマで区切ります。 また、引数のないメソッドの場合、カッコの中を空欄にします (カッコ自体は省略できません)。

同じ型の引数であっても int a, b のような書き方は間違いです。 引数ひとつごとに int a, int b のようにし、 別々に型を書かなければいけません。

メソッド宣言の最初の一行の例をいくつか示しておきます。

static int result(int a) { ...
static int compute(int subject, int times) { ...
static int getInfo() { ...
static double measure(double x, int t) { ...
static int summarize(int[] a) { ...

上の最後の例のように、引数に配列を渡したい場合、 「引数の宣言」の部分に配列を示す型 (int[] など) を書き、 続けて配列名を書きます。

メソッドの本体の書き方はこれまでのプログラムと同じです。 通常の変数宣言や、文を並べて書けば良いのです。

例題1: 組み合わせ数の計算

先ほど取り上げた nm の計算をするプログラムの全体を示します。 ファイル名は Combination.java とします。

class Combination {
    public static void main(String[] args) {
        int n = 5;
        int m = 2;

        int combination = fact(n) / (fact(m) * fact(n - m));
        System.out.println("Combination of " + n + ", " + m + " = " + combination);
    }

    static int fact(int x) {
        int f = 1;

        for (int i = 1; i <= x; i++)
            f = f * i;
        return f;
    }
}

return 文

メソッドから値を返すことを記述するための文が return 文です。 return 文の形式は次のとおりです。

return  式 ;

return の後に返したい値を求める式を書きます。 この式は、 そのメソッドの先頭で宣言した結果の値と同じ型の式である必要があります。

return 文を実行すると、その時点でメソッドの実行は終わりになり、 メソッドを実行した相手にプログラムの処理が戻ります。 return という言葉には「返す」と「戻る」両方の意味が含まれている と考えれば良いでしょう。

例題2: 2 つの数の大きい方 (最大値) を返すメソッド

2 つの数の大きい方を求める計算をメソッドとして独立させます。 このメソッドを利用して、 3 つの変数の最大値を求めるプログラムを書いてみます。 ファイル名は Max3d.java とします。

class Max3d {
    public static void main(String[] args) {
        int x = 5;
        int y = 2;
        int z = 3;

        System.out.println("最大値は " + maximum(x, maximum(y, z)) + " です。");
    }

    static int maximum(int a, int b) {
        if (a > b)
            return a;
        else
            return b;
    }
}

メソッド maximum の動きは単純です。 2 つの数 a と b を比較し、大きい方の値を返します。 if 文でどちらの場合に進んでも必ず return 文で値を返すようにしています。

このメソッドを利用すれば main メソッドは簡単です。 まず、yz の最大値を求め、 その結果と x の最大値を求めれば良いのです。

これまでのプログラムでは、 表示の際に結果を一時的に変数に代入していましたが、 今回は表示できればそれでよいということで、 System.out.println の中にメソッドの実行を記述しました。 メソッドの計算結果の値が System.out.println で表示されることになります。

引数

引数 (ひきすう) は、 メソッドの実行元からメソッド内へ値を引き渡す手段です。

メソッドの中から見た引数、 例えば fact のメソッド宣言における変数 x を仮引数 (かりひきすう) と呼びます。 一方、実行元から引数に渡すもの、 例えば main の中の fact に渡している nmn - m のことを実引数 (じつひきすう) と呼びます。

メソッドの中では、 仮引数は、実引数によって初期値が決まっているというだけで、 普通の変数と同様に使うことができます。

引数を使ってメソッドから実行元に値を返したい場合もあります。 整数と実数の場合、これは今のところ不可能であると考えてください。 引数は実行元からメソッド内へ一方通行で値を渡すことだけができ、 一度値が渡された後は、それぞれは別の変数として使うことになります。 一方で値を変更しても、その影響が他方に及ぶことはありません。

たとえば、例題 1 におけるメソッド fact は、 次のように書いても構いません。

static int fact(int x) {
    int f = 1;
    while (x > 0) {
        f = f * x;
        x--;
    }
    return f;
}

メソッド fact では、 仮引数である変数 x の値を変化させながら計算を行っていますが、 メソッドの実行元である main メソッドでの nm には影響はないのです。

ただし、配列を引数とした場合、 メソッド内で仮引数の値を変えると、 実行元から見た実引数の値も変化してしまいます。 このように、 引数を使った値の受渡しについては、少し変則的な決まりがあります。 詳しい説明は後まわしにしますが、 このことを頭の隅に入れておいてください。

結果を返さないメソッド

結果の値を返さないメソッドというものも宣言できます。 計算結果を返さないメソッドなど何の役に立つのだろうか、 という疑問がわいてくるかも知れません。 この種のメソッドは、引数を受け取り所定の仕事を行うだけで、 結果の値を返すことはしません。 プログラムの一部の作業を抜き出して、 一つの作業の単位とする目的には、 結果を返さないメソッドが向いている場合があります。

Java では、結果を返すものも結果を返さないものも 同じ「メソッド」という名前で呼びます。 しかし、他のプログラミング言語では、 結果を返す方を関数、結果を返さない方を サブルーチンやプロシージャなどの名前で区別することもあります。

値を返さないメソッドの実行

結果を返すメソッドの実行は、式の一部として例えば次のように書きました。

f = fact(x);

結果を返さないメソッドは、例えば次のようにメソッドの実行を単体で書きます。

printInfo(x);

結果を返さないメソッドの宣言

メソッドの中身の宣言の方法は、値を返すメソッドとほとんど変わりません。 その場合、「結果の型」に void と書きます。 以下にメソッド宣言の最初の一行の例をいくつか示します。

static void printInfo() { ...
static void setCoordinates(int x, int y) { ...

もう一つの違いは、 return 文の書き方です。 結果を返さないメソッドの中の return 文には式を書かずに、 単に return; とだけ書きます。 また、 return 文を書かなくてもメソッドの最後まで実行されると、 そこから実行元に自動的に戻ることになっています。

例題3:

次のような三角形を画面に表示するプログラムを作成します。

*.....
**....
*.*...
*..*..
*...*.
******

三角形をアステリスク (*) で表示し、 それ以外の背景をピリオド (.) で埋めつくしています。 このような出力をするにはアステリスクやピリオドを複数個連続して 出力するという作業を何回も行う必要があります。 この部分をメソッドとして独立させれば良いということになります。

class Triangle2 {
    public static void main(String[] args) {
        int size = 5;

        System.out.print('*');
        repeatCharactors(size, '.');
        System.out.println();
        for (int i = 1; i <= size - 1; i++) {
            System.out.print('*');
            repeatCharactors(i - 1, '.');
            System.out.print('*');
            repeatCharactors(size - i, '.');
            System.out.println();
        }
        repeatCharactors(size+1, '*');
        System.out.println();
    }

    static void repeatCharactors(int count, char charactor) {
        for (int i = 0; i < count; i++) 
            System.out.print(charactor);
    }
}