1 つの名前、複数のバージョン
前のページでは、メソッドの引数が「何を受け付けるか」を定めることを見ました。メソッドオーバーロードはそれをさらに進めます。引数リストが異なってさえいれば、複数のメソッドに同じ名前を付けられるのです。コンパイラはそれらを別々のメソッドとして扱い、渡された引数に基づいて正しいものを選びます。
だからこそ System.out.println は int、String、boolean、double のいずれも難なく出力できます。println が 1 つだけあるのではなく、同じ名前を共有する多数のオーバーロードがあるのです。あなたは意図した呼び出しを書き、コンパイラがそれに合うバージョンへ対応付けます。
どちらのメソッドも名前は square ですが、一方は int を、もう一方は double を取ります。リテラル 5 は int なので最初のオーバーロードが実行され、2.5 は double なので 2 つ目が実行されます。
何が別のオーバーロードとみなされるか
オーバーロードは引数リストで区別されなければなりません。つまり、少なくとも次のいずれかが必要です。
- 引数の個数が異なる、
- 引数の型が異なる、または
- 型の順序が異なる。
各呼び出しの引数リストは 3 つの join メソッドのうちちょうど 1 つと完全に一致するため、混乱は起きません。
戻り値の型はカウントされない
初心者がよく陥る落とし穴:戻り値の型だけでオーバーロードしようとすることです。戻り値の型はコンパイラが使うシグネチャの一部ではありません。そのため、これはコンパイルできません。
// コンパイルできない - 同じ名前、同じ引数で、戻り値の型だけが異なる
static int value() { return 1; }
static double value() { return 1.0; } // エラー: value() はすでに定義されている
value() のような呼び出し箇所では、引数のどこにもどちらを呼びたいかの手がかりがないため、コンパイラは区別できません。オーバーロードに異なる戻り値の型を与えることはできますが、それは引数リストがすでに異なっている場合に限ります。
Java はどのようにオーバーロードを選ぶか
複数のオーバーロードが引数を受け付け得る場合、Java は最も具体的なものを選び、拡大変換よりも完全な型一致を優先します。int 引数で何が起きるか見てみましょう。
show(7) は、拡大すれば long や double も 7 を保持できるにもかかわらず、int に完全一致します。完全一致するオーバーロードが取り除かれて初めて、コンパイラは int を long へ、次いで double へと拡大します。この解決は完全にコンパイル時に、引数の宣言された型に基づいて決定されます。
あいまいな呼び出しに注意
どのオーバーロードも明らかに最良の一致ではない場合、コンパイラは推測を拒否し、エラーを報告します。これは、どの参照型にも当てはまる null で最も多く起こります。
static void handle(String s) { }
static void handle(StringBuilder b) { }
handle(null); // エラー: handle への参照があいまい
両方のオーバーロードが null を受け付け、どちらもより具体的ではないため、この呼び出しはコンパイルできません。キャストで型を明示するか - handle((String) null) - あるいはオーバーロードが衝突しないように設計し直して修正します。オートボクシングと拡大を混ぜるときも同じ注意が必要です。すべての呼び出しに明らかな勝者が 1 つあるよう、オーバーロードの集合を十分にシンプルに保ちましょう。
コンストラクタのオーバーロード
オーバーロードは通常のメソッドに限りません。コンストラクタは、オブジェクトを構築するいくつかの方法を提供するために常にこれを使います。引数なしのコンストラクタは、this(...) を使ってより完全なコンストラクタに処理を委譲できます。
2 つのコンストラクタは Point という名前を共有しますが、引数の個数が異なります。これはオーバーロードされたメソッドとまったく同じです。this(...) で委譲することで、初期化ロジックが 1 か所にまとまります。
オーバーロード対オーバーライド
この 2 つは似た響きですが、互いに無関係です。
- オーバーロード - 同じ名前、異なる引数リスト、同じクラス内。コンパイラがコンパイル時にバージョンを選びます。ある操作のバリエーションを提供することが目的です。
- オーバーライド - サブクラスが継承したメソッドを同じ名前かつ同じ引数で再定義します。Java はオブジェクトの実際の型に基づいて実行時にバージョンを選びます。振る舞いを置き換えることが目的です(継承とポリモーフィズムの中で出会います)。
引数リストが同一ならオーバーライドしている(または同じクラス内で重複メソッドエラーを起こしている)ことになり、異なればオーバーロードしていることになります。
次へ:Varargs
オーバーロードを使えば join(a, b) と join(a, b, c) を別々のメソッドとして書けます。しかし、個数ごとにオーバーロードを宣言することなく、任意の個数の引数を受け付けたい場合はどうでしょうか。Java の varargs 構文は、1 つのメソッドが可変長の引数リストを取れるようにします。それが次のページのテーマです。
よくある質問
Java のメソッドオーバーロードとは何ですか?
メソッドオーバーロードとは、同じクラス内で同じ名前のメソッドを複数定義し、それぞれに異なる引数リスト(引数の個数の違い、型の違い、または型の順序の違い)を持たせることです。コンパイラは、渡された引数を各オーバーロードの引数と照合して、どのバージョンを呼び出すかを決定します。これは実行時ではなくコンパイル時の判断です。
Java では 2 つのメソッドが戻り値の型だけで区別できますか?
いいえ。戻り値の型はオーバーロードにおけるメソッドのシグネチャの一部ではないため、同じクラス内の int total() と double total() はコンパイルエラーになります。オーバーロードは引数リスト、つまり引数の個数・型・順序で区別されなければなりません。戻り値の型は異なってもかまいませんが、それは引数の違いに加えての場合だけで、それ単独ではいけません。
Java におけるオーバーロードとオーバーライドの違いは何ですか?
オーバーロードは、同じクラス内で同じ名前だが引数が異なる複数のメソッドで、コンパイル時にコンパイラが解決します。オーバーライドは、サブクラスが継承したメソッドを同じ名前かつ同じ引数で再定義することで、オブジェクトの実際の型に基づいて実行時に解決されます。オーバーロードはバリエーションを提供するためのもの、オーバーライドは振る舞いを置き換えるためのものです。