オーバーロード

同じ名前の複数のメソッドを宣言するオーバーロードについて学習しましょう。

メソッドのオーバーロードとシグネチャ

変数の有効範囲のところで登場したScopeSample02では、2数の最大値を返すメソッドmaxを宣言していました。 このときの2数はint型となっていましたが、double型用の同じ機能のメソッドを追加することを考えてみましょう。 int用とdouble用の2つのメソッドをintMaxとdoubleMaxのように名前を変えて区別するとしたらどうでしょうか。 さらに、2数の最大値だけでなく、3数の最大値を返すメソッドも作るとしたら、intMax2, intMax3, doubleMax2, doubleMax3のようにするのでしょうか。 これでは、いちいち名前をつけるのも面倒ですし、メソッドを使う側も不便です。 そういうメソッドは、いっそすべて同じ名前にする方が良いでしょう。

同一クラス内で同じ名前の複数のメソッドを宣言することをオーバーロード(多重定義 : overloading)と言います。 ただし、オーバーロードできるのは、シグネチャの異なるメソッドだけです。 シグネチャ(signature)とは、メソッドの名前と仮引数の個数と型を合わせたものです。 戻り値の型はシグネチャには含まれません。

図 9-15 : メソッドのシグネチャ

もしも同じシグネチャを持つメソッドを2つ以上宣言した場合は、コンパイル時にエラーと成ります。 それは、メソッド呼び出し時に、どのメソッドを呼び出せばよいのかを明確に指定できないからです。

次のMethodSample14では、2つのdivideメソッドが宣言されています。 これらのメソッッドはシグネチャが同じで、異なるのは戻り値の型だけです。 従って、MethodSample14をコンパイルしようとするとエラーとなります。

MethodSample14.java
class MethodSample14 {
    /*---- [1] : aをbで割ったint型の商を返す ----*/
    static int divide(int a, int b){
        return a / b;
    }

    /*---- [2] : aをbで割ったdouble型の商を返す ----*/
    static double divide(int a, int b){
        return (double)a / b;
    }

    public static void main(String[] args){
        System.out.println("7 / 2 = " + divide(7, 2));
    }
}

ここでは、divide(7, 2) という形でメソッドを呼び出していますが、[1]と[2]のどちらを呼び出したいのか分かりませんね。

もちろん、一方のdivideメソッドをコメントアウトすれば、コンパイルすることができます。 実行結果は次のようになります。

MethodSample14の実行結果([1]のdivideメソッドをコメントアウトした場合)
7 / 2 = 3.5
MethodSample14の実行結果([2]のdivideメソッドをコメントアウトした場合)
7 / 2 = 3

次のMethodSample15のように、仮引数の名前だけが異なる場合もコンパイル時にエラーとなります。

MethodSample15.java
class MethodSample15 {
    /*---- [1] : aをbの和を返す ----*/
    static int add(int a, int b){
        return a + b;
    }

    /*---- [2] : xをyの和を返す ----*/
    static int add(int x, int y){
        return x + y;
    }

    public static void main(String[] args){
        System.out.println("5 + 9 = " + add(5, 9));
    }
}

やはり、いずれかのaddメソッドをコメントアウトすればコンパイルできます。 実行結果は次のようになります。

MethodSample15の実行結果([1]か[2]の一方のaddメソッドをコメントアウトした場合)
5 + 9 = 14

メソッドの名前と仮引数の個数と型を合わせたものをシグネチャという。 戻り値の型はシグネチャには含まれない。

オーバーロードされたメソッドを使ってみる

では、正真正銘のオーバーロードを使ったプログラムを見てみましょう。 MethodSample16では、シグネチャの異なる4つのmaxメソッドが宣言されています。

MethodSample16.java
class MethodSample16 {
    /*---- [1] : int型のa, bの最大値を返す ----*/
    static int max(int a, int b){
        return a > b ? a : b;
    }
    
    /*---- [2] : int型のa, b, cの最大値を返す ----*/
    static int max(int a, int b, int c){
        int max = a;
        if(b > max) max = b;
        if(c > max) max = c;
        return max;
    }
    
    /*---- [3] : double型のa, bの最大値を返す ----*/
    static double max(double a, double b){
        return a > b ? a : b;
    }
    
    /*---- [4] : double型のa, b, cの最大値を返す ----*/
    static double max(double a, double b, double c){
        double max = a;
        if(b > max) max = b;
        if(c > max) max = c;
        return max;
    }
    
    public static void main(String[] args){
        System.out.println("1, -2の最大値は" + max(1, -2) + "です。");
        System.out.println("1, -2, 5の最大値は" + max(1, -2, 5) + "です。");
        System.out.println("0.5, -2.1の最大値は" + max(0.5, -2.1) + "です。");
        System.out.println("0.5, -2.1, 5.4の最大値は" + max(0.5, -2.1, 5.4) + "です。");
    }
}
MethodSample16の実行結果
1, -2の最大値は1です。
1, -2, 5の最大値は5です。
0.5, -2.1の最大値は0.5です。
0.5, -2.1, 5.4の最大値は5.4です。

[1]から[4]の4つのmaxメソッドが宣言されています。 それらのどの2つを比べても、シグネチャが異なっていることがおわかりいただけるでしょうか。 例えば、[1]と[2]では引数の個数が異なり、[1]と[3]では引数の型が異なります。

同一クラス内で同じ名前の複数のメソッドを宣言することをオーバーロードという。 ただし、オーバーロードできるのは、シグネチャの異なるメソッドだけである。

デフォルト値を使うオーバーロード

今回もメソッドの戻り値のところで登場した、sumUpメソッドを題材にしてみましょう。 sumUpメソッドは初項mから末項nまでのすべての整数の和を求めるメソッドでしたね。

sumUpメソッドの使われ方として一番多いのは、やはり初項mの値が1、つまり、1 + 2 + 3 + ... + nのような計算をするケースでしょう。 だとしたら、末項nを引数として与えただけで、1からnまでの和を求めてくれるsumUpメソッドも宣言しておけば便利でしょう。 そのサンプルプログラムがMethodSample17です。

MethodSample17.java
class MethodSample17 {
    /*---- [1] : 初項mから末項nまでの和を返す ----*/
    static int sumUp(int m, int n){
        int sum = 0;
        for(int i = m; i <= n; i++){
            sum += i;
        }
        return sum;
    }

    /*---- [2] : 初項1から末項nまでの和を返す ----*/
    static int sumUp(int n){
        int sum = 0;
        for(int i = 1; i <= n; i++){
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args){
        System.out.println("1から10までの和は" + sumUp(10) + "です。");
        System.out.println("-2から4までの和は" + sumUp(-2, 4) + "です。");
        System.out.println("-5から2までの和は" + sumUp(-5, 2) + "です。");
    }
}
MethodSample17の実行結果
1から10までの和は55です。
-2から4までの和は7です。
-5から2までの和は-12です。

新しいsumメソッドも問題なく実行されていますね。 しかし、ここで一つ不満を感じます。 2つのsumメソッドの中身をよく見比べてください。 違いはただ1カ所だけです。 for文の初期化部が、[1]ではi = mとなっていますが、[2]ではi = 1となっています。 たったこれだけの違いのために、そっくりなメソッドを書くのは無駄だと思いませんか。

こんなときは、次のMethodSample17Aのようにすればよいのです。

MethodSample17A.java
class MethodSample17A {
    /*---- [1] : 初項mから末項nまでの和を返す ----*/
    static int sumUp(int m, int n){
        int sum = 0;
        for(int i = m; i <= n; i++){
            sum += i;
        }
        return sum;
    }

    /*---- [2] : 初項1から末項nまでの和を返す ----*/
    static int sumUp(int n){
        return sumUp(1, n);
    }

    public static void main(String[] args){
        System.out.println("1から10までの和は" + sumUp(10) + "です。");
        System.out.println("-2から4までの和は" + sumUp(-2, 4) + "です。");
        System.out.println("-5から2までの和は" + sumUp(-5, 2) + "です。");
    }
}

MethodSample17Aの実行結果は、MethodSample17と同じです。

MethodSample17Aでも2つのsumメソッドが宣言されていますが、[2]の宣言の仕方に特徴があります。 引数mを指定しない[2]のsumメソッドは、1というデフォルト値とnを[1]のsumメソッドに渡し、その戻り値を返しているだけなのです。 [2]は[1]を活用して宣言されているわけです。 プログラミングにおいては、このような手法がよく使われます。 これは、無駄を省き、プログラムを読みやすくし、しかもメンテナンスも楽にする賢い手法だと言えるでしょう。