メソッド (2)

値を返すメソッド

前回の講義では、メソッドを「いくつかの命令を束ねたもの」という視点で見ており、メソッドを起動することによって束ねられた命令の列を実行することができた。今回は、メソッドを「いくつかの命令を束ねた」として扱う。

今まで紹介した式は、以下のようなものがある。

メソッドの起動 (呼び出し) を式として扱えるようになると、例えば次のようなプログラムが書けるようになる。

double x = 10.5;
double y = f(x) + 3.5;

上記は 実引数 x でメソッド f を呼び出している。

これまでに使ったメソッド

これまでにも値を返すメソッドを使用してきた (これらはJavaに元々用意されている)。

y = Math.sqrt(2.0);

これは、Math (というclass) の sqrt というメソッドを使っている。カッコ内がこのメソッドに渡される引数 (ひきすう:パラメタparameter) であり、今の場合 double 型の実数 2.0 が引数である。この文を実行すると 2.0 を引数としてメソッド sqrt が実行される (メソッドを起動する、呼び出すとも言う)。その実行が終わると 1.4142135623730951 が返される(1.4142135623730951 という戻り値を持ってメソッドから帰ってくる)。その値が変数 y に代入される。

「メソッド (method)」という呼び方は、オブジェクト指向プログラミング言語で使われる (Java はオブジェクト指向プログラミング言語である)。他のプログラミング言語では、「手続き (procedure)」、「関数 (function)」、「サブルーチン (subroutine)」などと呼ばれる。

数学で、関数f(x)を

f(x) = 3x + 1

と定義し、

7 + f(2)

の計算をするときは、定義式のxのところに2を入れて計算した結果

f(2) = 3 * 2 + 1 = 7

を7に加えて14を得る。

ここで、xは整数であるとして、これと同じことをJavaで書くと、おおよそ次のようになる (これではまだ動かない)。

int f(int x) { return 3*x + 1; } // 最初のintは結果の値が整数であること
                                  // int x は引数が整数であることを示す
...
a = 7 + f(2);

メソッドの宣言 (定義)

次のプログラムでは、squareというメソッドを作っている。それは引数を二乗した値を返すメソッドである。

public class SquareMethod {

    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            System.out.println(square(i));
        }
    }
    
    public static int square(int x) {
        return x * x;
    }
}
System.out.println(square(i));

の中の"square(i)" が、i を引数としてメソッドsquareを呼び出すことを意味する。

public static int square(int x)

の中の "int x" は、引数の型が int 型であり、その名前 (このメソッドの中で使われる引数の名前) を x とする、という宣言である。そして、 "public static int" の最後にある int は、メソッド square が実行し終わった際に戻ってくる値 (戻り値とも呼ぶ) の型を表す。

例えば、square(3) とやると、まず square メソッドの仮引数 x に 3 が代入される。そして 3 * 3 が計算され、return という命令によって計算結果の 9 が square(3) を起動した側に返される。

System.out.println(square(i));

では、squareメソッドの仮引数 x に現在の i の値を代入し、square メソッドの中身を実行する。return 文によって i を二乗した値が返され、その値がSystem.out.println命令によって表示される。

つまり、下記のような命令が実行されているようなイメージになる。

System.out.println(<iを二乗した値>);

つまり、プログラムを実行しているとメソッドを起動した部分は、return 文で返される値 (戻り値) に置き換わって評価される。

まとめると、メソッドを呼び出すと、実引数の値が仮引数に代入されてメソッドの本体が実行される。return文で返された戻り値がそのメソッド呼び出しの値となる。

メソッドの定義の形は、今のところは以下、

public static 戻り値の型 メソッド名(仮引数の宣言の列){
    メソッドの本体
}

の形である。前回の講義では「戻り値の型」の部分が void であった。void とは「何もない」を表す型で、そう考えると戻り値型が void のメソッドを「何もないを返す (値を何も返さない)」とすることができる。何か値を返すメソッドを作りたい場合、戻り値の型に int や double などを入れてやればよい。

たとえば、引数に (double, int) を取り、double 型の値を返すメソッド power は、次のように書ける。

public class Power {

    public static void main(String[] args) {
        System.out.println(power(3.0, 4));
    }
    
    // a の b 乗を計算するメソッド
    public static double power(double a, int b) {
        double product = 1;
        // a を b 回掛けたものを作る
        for (int i = 0; i < b; i++) {
            product *= a;
        }
        return product;
    }
}

return 文

return 文は、戻り値の型が void 以外のときにメソッドを呼び出した元に値を返す命令であった。

public static int square(int x) {
  return x * x;
}

これは、戻り値が void 型のメソッドでも使用することができる。ただし、値を返すことはできないので、 return <式>; の形ではなく、 return; という形で使用する。

public static void printOnNotZero(int x) {
  if (x == 0) {
    return;
  }
  System.out.println(x);
}

return文が実行されると、その場でそのメソッドの実行は終了する。

public static void printInt(int x) {
  if (x == 0) {
    System.out.println(0);
    return;
  }
  System.out.println(x);
}

上記のようなメソッドを printInt(0) とやって呼び出すとすると、

0
0

ではなく

0

とだけ表示される (ifの中のreturn文でメソッドが終了する)。

戻り値が void 型でないメソッドは必ず何らかの値を返さなければならない。値を返さない可能性がある場合はコンパイルエラーとなる。

public static int square(int x) {
  if (x == 1) {
    return 1;
  }
}

例えば、上記のようなメソッドはコンパイルエラーとなる。これは、x == 1 が成立しないときに return 命令を実行できないからである。

戻り値の型による動作の違い

戻り値の型が void であるものと、void 以外のもの (int, doubleなど) では、呼び出し時に少しだけ動作が違う。

まず、戻り値の型が void であるメソッドの呼び出しは、式の一部として使ってはいけない。

つまり、以下のようなプログラムは間違いであり、コンパイルエラーとなる。

public static void main(String[] args) {
  System.out.println(voidMethod() + 5);
}

public static void voidMethod() {
  System.out.println(10);
}

戻り値の型が void あるメソッドの呼び出しは、それだけで命令文として使われる。戻り値の型が void 以外であるメソッドの呼び出しは、通常は式の一部として使われるが、それだけで命令文としてもよい。

つまり、次のようなプログラムは正しい。

public static void main(String[] args) {
  intMethod();
}

public static int intMethod() {
  System.out.println(10);
  return 0;
}

この場合、intMethod から返される値 0 は、誰も使用せずに捨てられる。

メソッドの多重定義 (overloading)

同じ名前を有するメソッドを引数の個数や型を変えることにより多重に定義することができる。たとえば

public static int square( int x ) {
  return x * x;
}
public static double square( double y ) {
  return y * y;
}

のように同じ名前のメソッドを、引数の個数や型を変えて複数個定義することが出来る。それをメソッドの多重定義 (オーバーロード)という。今の例では引数の個数は同じで型が異なる。そのメソッドの呼び出しがあったときは、その実引数の個数や型によって、どちらのメソッド呼び出しかが区別される。たとえば上の例で、square(3) なら1番目のsquareの呼び出しであり、square(2.5) なら2番目のsquareの呼び出しである。すなわち、その実引数の個数と型に一致する(順序も含めて) 仮引数をもったメソッドの呼び出しになる。

public static int square(int x) {
  System.out.
  return x * x;
}
public static double square(double y) {
  return y * y;
}

例えば、次のようなプログラムを書いてみる。

public class Overloading {

    public static void main(String[] args) {
        // square(int)
        System.out.println(square(10));
        
        // square(double)
        System.out.println(square(10.5));
    }
    
    public static int square(int x) {
        System.out.println("square(int)");
        return x * x;
    }

    public static double square(double x) {
        System.out.println("square(double)");
        return x * x;
    }
}

このプログラムを実行すると、以下のように表示される。

square(int)
100
square(double)
110.25

上記の例では引数の型を変えて多重定義している。他にも、引数の個数を変えて多重定義することもできる。

public class Overloading2 {

    public static void main(String[] args) {
        // max(int)
        System.out.println(max(10));

        // max(int, int)
        System.out.println(max(10, 20));

        // max(int, int, int)
        System.out.println(max(10, 30, 20));
    }
    
    // 1つの中の最大
    public static int max(int a) {
        return a;
    }
    
    // 2つの中の最大
    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        else {
            return b;
        }
    }
    
    // 3つの中の最大
    public static int max(int a, int b, int c) {
        // max(int, int) を 2回使う
        return max(a, max(b, c));
    }
}