メソッドのオーバーロード
メソッドのオーバーロード とは
同じ名前で引数違いの複数バージョンを定義する「オーバーロード」を学び、足し算メソッド add の 3 連発を題材にシグネチャの考え方を体に馴染ませる。
メソッドに「同じ名前で違うバージョン」を持たせる
ここまでのレッスンで、Java のメソッドは「戻り値の型 名前 (引数リスト)」というシグネチャを持つことを学んできました。今回はその応用編として、メソッドのオーバーロード という仕組みを取り上げます。オーバーロードとは、ひと言で言えば「同じクラスの中に、同じ名前 で 引数違い のメソッドを複数本並べて定義できる」機能のことです。英語で overload は「過剰に積む」「重ねる」という意味で、その名のとおり 1 つの名前に複数の機能を重ね合わせるイメージで覚えてください。
身近な例で考えてみましょう。電卓に add というボタンがひとつだけ付いていて、それを押すと足し算が走るとします。このボタンを「数を 1 つだけ入れたら 0 を足す版」「2 つ入れたら 2 つを足す版」「3 つ入れたら 3 つを足す版」のように使い分けられたら便利ですよね。Java ではまさにこれを 1 つのメソッド名で実現できます。それがオーバーロードです。
オーバーロードを使うと、呼び出す側は「何を渡せば何が返るか」だけを意識すればよくなり、「
addかadd2かadd3のどれを呼ぶんだっけ?」と名前で悩む必要がなくなります。APIを設計するときの強力な武器であり、Javaの標準ライブラリでもString.valueOfMath.maxSystem.out.printlnなど至るところで使われています。
本レッスンでは、同じクラスの中に下記の 3 つの add メソッドを並べる例を題材にしながら、オーバーロードの仕組み・ルール・落とし穴を一通り眺めていきます。
add(int a)— 引数 1 個。aをそのまま返す版 (+ 0版)add(int a, int b)— 引数 2 個。a + bを返す版add(int a, int b, int c)— 引数 3 個。a + b + cを返す版
オーバーロードの正体は「シグネチャの違い」
Java がオーバーロードされたメソッドを正しく見分けられるのは、メソッドシグネチャ という単位でメソッドを管理しているからです。シグネチャとは下記の組み合わせのことで、これが 1 本ずつユニークである限り、いくつでも同じ名前のメソッドを並べられます。
- メソッド名 (例:
add) - 引数の 個数
- 引数の 型 と 並び順
注意してほしいのは、「戻り値の型 はシグネチャには含まれない」という点です。戻り値だけが違うメソッドは、Java から見ると「同じシグネチャ」なのでオーバーロードできません。たとえば下記のような 2 本は、add という名前と (int, int) という引数リストが同じなので、コンパイルエラーになります。
Java
public class Solution {
public static int add(int a, int b) { return a + b; }
public static double add(int a, int b) { return a + b; } // ✗ 戻り値だけ違う
}「戻り値はシグネチャに含まれない」というルールは最初は意外かもしれませんが、考えてみると当然です。
add(2, 3)と書いた時点で、コンパイラはまだ「結果を何で受けるか」を知りません。引数だけで一意にメソッドを選べないと困るのです。
3 連発の add を書いてみる
それでは実際に、同じクラスの中に 3 つの add を並べてみます。下記のコードがオーバーロードの典型例です。
Java
public class Calculator {
public static int add(int a) {
return a;
}
public static int add(int a, int b) {
return a + b;
}
public static int add(int a, int b, int c) {
return a + b + c;
}
}メソッド名はすべて add ですが、引数の 個数 が違うので、Java から見ると別の 3 本として扱われます。呼び出し側は下記のように、自然な引数の数で呼び分けられます。
Java
int x = Calculator.add(10); // → 10 (1 引数版が選ばれる)
int y = Calculator.add(10, 20); // → 30 (2 引数版)
int z = Calculator.add(10, 20, 30); // → 60 (3 引数版)コンパイラは引数の数と型を見て、自動的に「どの add を呼ぶか」を決めてくれます。プログラマはどれを呼ぶか名前で悩む必要がなく、ただ自然に add(...) と書けばよい、というのがオーバーロードの嬉しさです。
コンパイラの判定の流れ
オーバーロード解決 (overload resolution) と呼ばれる、コンパイラ内部での判定の流れを図にすると下記のとおりです。Calculator.add(10, 20) を呼び出した瞬間、コンパイラは候補から 1 本を絞り込みます。
図のとおり、まず引数の 個数 で大きく分岐し、同じ個数の候補が複数ある場合は 型 でさらに絞り込みます。「型が完全に一致する版」がいれば真っ先に選ばれ、なければ自動変換 (例: int → long への昇格) を試みます。それでも見つからなければコンパイルエラーです。
引数の型違いによるオーバーロード
オーバーロードは「引数の個数違い」だけでなく、「引数の 型違い」でも成立します。たとえば下記のように、int 版と double 版を 2 本並べることも普通に行われます。
Java
public class MathLike {
public static int max(int a, int b) {
return a >= b ? a : b;
}
public static double max(double a, double b) {
return a >= b ? a : b;
}
}このとき、MathLike.max(3, 5) のように int を渡せば int 版が、MathLike.max(3.14, 2.71) のように double を渡せば double 版が選ばれます。Java 標準ライブラリの Math.max も、まさにこの形で int long float double の 4 種類が用意されています。
オーバーロードがあるおかげで、
Math.max(1, 2)とMath.max(1.5, 2.5)のように、整数でも小数でも同じ名前で書けます。ユーザー視点では「とりあえずMath.maxを使えばよい」と覚えるだけで済み、学習コストが大きく下がるのです。
よくある間違い
オーバーロードは強力ですが、書き始めると下記のような落とし穴に何度かハマります。「あるある」をあらかじめ知っておくと、エラーメッセージの意味も読み取りやすくなります。
1 つ目は、戻り値の型だけを変えてオーバーロードしようとする ミスです。int add(int a, int b) と double add(int a, int b) のように、引数が完全に同じで戻り値だけ違うメソッドを 2 本並べると、Java は「add(int, int) というシグネチャがすでに定義されている」と判断してエラーを出します。エラーメッセージは英語で method add(int,int) is already defined in class Solution のような形で出ます。戻り値はシグネチャに含まれない、というルールを思い出してください。
2 つ目は、曖昧な呼び出し (ambiguous call) を作ってしまうミスです。たとえば下記のような 2 本を定義した上で、Calculator.add(1, 2L) のように int と long を混ぜて呼ぶと、コンパイラは「どちらの版に揃えるべきか」を一意に決められず迷子になります。
Java
public static int add(int a, long b) { return (int)(a + b); }
public static int add(long a, int b) { return (int)(a + b); }このとき、コンパイラは reference to add is ambiguous というメッセージを出して止まります。曖昧さが残る設計は、人間が読んでも「どれが呼ばれるんだっけ?」と混乱の元になるので、最初から避けるのが安全です。
3 つ目は、引数名だけを変えて同じシグネチャにしてしまう ミスです。下記の 2 本は一見違うメソッドに見えますが、Java から見るとどちらも add(int, int) という同じシグネチャなので、コンパイルエラーになります。
Java
public static int add(int a, int b) { return a + b; }
public static int add(int x, int y) { return x + y; } // ✗ 引数名だけ違うシグネチャに含まれるのはあくまで 型と並び順 であって、引数の 名前 は含まれません。「a と b を x と y に変えれば別のメソッドになるのでは」と期待してしまいがちですが、それは通用しません。
もうひとつ、初心者が困りやすいのが「同じ名前のメソッドを別ファイルに書いていいか」という疑問です。オーバーロードはあくまで 同じクラスの中 での話で、別のクラスにある同じ名前のメソッドは、そもそもオーバーロードではなく単に「無関係な別メソッド」になります。
やってみよう
ここからが本番です。下記の手順で課題に挑戦してみましょう。
- 右側のエディタで
starter_codeを開く Solution.add2(int a, int b)の中身をreturn a + b;に書き換える- 実行ボタンを押して、テストが全て緑になることを確認する
基本の add2 が通ったら、下記の発展課題にも触れてみてください。Java のコードが手に馴染んでいきます。
- 同じ
Solutionクラスにadd(int a)add(int a, int b)add(int a, int b, int c)の 3 本を書き足してみる (テストはadd2を見ているので、追加しても fail しません) Calculator.add(10)Calculator.add(10, 20)Calculator.add(10, 20, 30)をそれぞれ呼んだとき、コンパイラがどの版を選んでいるかを意識してみる- 試しに同じシグネチャの
int add2(int a, int b)をもう 1 本書いてみて、どんなエラーが出るか観察する - 戻り値だけ違う
double add2(int a, int b)を書いて、なぜエラーになるか自分の言葉で説明してみる
小さなメソッドを 1 本書くだけですが、その裏側には「シグネチャ」「オーバーロード解決」「曖昧さの回避」という、Java でこれから何度も顔を出す重要な概念が詰まっています。今ここで体に馴染ませておくと、Math.max や String.valueOf のような標準ライブラリのソースを読むときも、すんなり頭に入ってくるはずです。一緒に進めていきましょう。
よくある質問
Q. Java と他言語の文法はどう違いますか?
A. Java は静的型付けで、変数宣言時に型を明示します(int x = 0;)。中括弧でブロックを表し、文末にセミコロンが必要です。Python の動的型付けや JavaScript の柔軟さに慣れていると最初は窮屈ですが、コンパイル時にバグが発見できる安全性がメリットです。
Q. コードが動かないときに最初に見るべき場所は?
A. コンパイルエラーは行番号とエラー種別(cannot find symbol、incompatible types など)が表示されます。実行時例外はスタックトレースの最初の at ... が原因行です。IDE(IntelliJ / VS Code)の警告も丁寧に潰すと、半分のバグは未然に防げます。
Q. Java の習得後に学ぶべき技術は何ですか?
A. 基本構文を抑えたら java-intermediate(コレクション / ジェネリクス / Stream / 例外)に進み、Spring Boot や Web 開発に展開するのが王道です。クラウド時代は Kotlin / Scala への展開も視野に入りますが、まずは Java 標準を固めるのが効率的です。
次のレッスン
次は メソッドクイズ で、メソッドクイズ を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- オーバーロード の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. オーバーロード とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- Solution クラスに
public static int add2(int a, int b)を実装すること - メソッドの中身は
a + bを計算し、結果を int でreturnすること - クラス名・メソッド名・引数の型と個数を変えないこと (テストは add2(int, int) のシグネチャに対して走る)
入出力例
test-cases.txt
add2(2, 3) → 5
add2(-1, 1) → 0
add2(10, 20) → 30
add2(0, 0) → 0
add2(-5, -7) → -12
add2(100, 250) → 350