メソッドのオーバーロード

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

メソッドのオーバーロード とは

同じ名前で引数違いの複数バージョンを定義する「オーバーロード」を学び、足し算メソッド add の 3 連発を題材にシグネチャの考え方を体に馴染ませる。

メソッドに「同じ名前で違うバージョン」を持たせる

ここまでのレッスンで、Java のメソッドは「戻り値の型 名前 (引数リスト)」というシグネチャを持つことを学んできました。今回はその応用編として、メソッドのオーバーロード という仕組みを取り上げます。オーバーロードとは、ひと言で言えば「同じクラスの中に、同じ名前引数違い のメソッドを複数本並べて定義できる」機能のことです。英語で overload は「過剰に積む」「重ねる」という意味で、その名のとおり 1 つの名前に複数の機能を重ね合わせるイメージで覚えてください。

身近な例で考えてみましょう。電卓に add というボタンがひとつだけ付いていて、それを押すと足し算が走るとします。このボタンを「数を 1 つだけ入れたら 0 を足す版」「2 つ入れたら 2 つを足す版」「3 つ入れたら 3 つを足す版」のように使い分けられたら便利ですよね。Java ではまさにこれを 1 つのメソッド名で実現できます。それがオーバーロードです。

オーバーロードを使うと、呼び出す側は「何を渡せば何が返るか」だけを意識すればよくなり、「addadd2add3 のどれを呼ぶんだっけ?」と名前で悩む必要がなくなります。API を設計するときの強力な武器であり、Java の標準ライブラリでも String.valueOf Math.max System.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 本を絞り込みます。

diagram (will load when visible)

図のとおり、まず引数の 個数 で大きく分岐し、同じ個数の候補が複数ある場合は でさらに絞り込みます。「型が完全に一致する版」がいれば真っ先に選ばれ、なければ自動変換 (例: intlong への昇格) を試みます。それでも見つからなければコンパイルエラーです。

引数の型違いによるオーバーロード

オーバーロードは「引数の個数違い」だけでなく、「引数の 型違い」でも成立します。たとえば下記のように、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) のように intlong を混ぜて呼ぶと、コンパイラは「どちらの版に揃えるべきか」を一意に決められず迷子になります。

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; } // ✗ 引数名だけ違う

シグネチャに含まれるのはあくまで 型と並び順 であって、引数の 名前 は含まれません。「abxy に変えれば別のメソッドになるのでは」と期待してしまいがちですが、それは通用しません。

もうひとつ、初心者が困りやすいのが「同じ名前のメソッドを別ファイルに書いていいか」という疑問です。オーバーロードはあくまで 同じクラスの中 での話で、別のクラスにある同じ名前のメソッドは、そもそもオーバーロードではなく単に「無関係な別メソッド」になります。

やってみよう

ここからが本番です。下記の手順で課題に挑戦してみましょう。

  1. 右側のエディタで starter_code を開く
  2. Solution.add2(int a, int b) の中身を return a + b; に書き換える
  3. 実行ボタンを押して、テストが全て緑になることを確認する

基本の 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.maxString.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 つができることを確認しましょう。

  1. オーバーロード の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. オーバーロード とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. Solution クラスに public static int add2(int a, int b) を実装すること
  2. メソッドの中身は a + b を計算し、結果を int で return すること
  3. クラス名・メソッド名・引数の型と個数を変えないこと (テストは 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

ヒント

main.java
main.java
学習モード

メモ

メソッドのオーバーロード

⌘S で保存