オーバーロードの全体像
オーバーロードは「同じメソッド名に、引数の型・個数・並びが異なるバリエーションを定義する」仕組みです。呼び出し時の引数からコンパイラが最適な版を選びます。意味が近い処理を一つの名前でまとめられるため、API が覚えやすく、読みやすさも向上します。Java には「デフォルト引数」や「名前付き引数」がないので、柔軟な受け方はオーバーロードや可変長引数で表現します。
基本構文と呼び分け
同名・異なるシグネチャ(型/個数/並び)
static int sum(int a, int b) { return a + b; }
static double sum(double a, double b) { return a + b; }
static int sum(int a, int b, int c) { return a + b + c; }
Java呼び出し側は引数の型・個数に応じて自動的に該当メソッドへルーティングされます。
可変長引数との併用
static int sum(int... nums) {
int s = 0;
for (int n : nums) s += n;
return s;
}
sum(1); // 1件
sum(1, 2, 3); // 複数件
sum(); // 0件
Java可変長(varargs)は「配列として受ける」特殊なオーバーロードです。必須項目がある場合は、必須を通常引数で先に受け、後半を可変長にすると意図が明確になります。
重要ポイントの深掘り:解決ルール(どれが選ばれるか)
最も適した版(最適一致→拡張→ボクシング→可変長)
- ぴったり型一致の版が最優先で選ばれます。
- 次に「プリミティブの拡張変換」(例:
int→long)が検討されます。 - その次に「ボクシング/アンボクシング」(例:
int↔Integer)。 - 最後の手段として可変長引数が選ばれます。
static void f(int x) { System.out.println("int"); }
static void f(long x) { System.out.println("long"); }
static void f(Integer x) { System.out.println("Integer"); }
static void f(int... x) { System.out.println("varargs"); }
f(10); // int
f(10L); // long
Integer ix = 10;
f(ix); // Integer
f(); // varargs
Java「曖昧」になる組み合わせはコンパイルエラーになります。意図が伝わる型で呼ぶか、明示キャストで選択を確定させましょう。
重要ポイントの深掘り:曖昧さ・落とし穴・ベストプラクティス
曖昧なオーバーロードは避ける
static void g(Long x) { }
static void g(Integer x) { }
// g(null); // どちらも受けられる → 曖昧でコンパイルエラー
Javanull を渡すと参照型版が複数候補になり曖昧になります。呼び出し側でキャストして明示するか、API 設計を見直します。
オートボクシングとプリミティブ拡張の競合
static void h(long x) { System.out.println("long"); }
static void h(Integer x) { System.out.println("Integer"); }
int n = 5;
h(n); // long が選ばれる(拡張優先、ボクシングより先)
Java「どちらが選ばれるか」を開発者が即答できない設計は避け、シグネチャを簡潔にしましょう。
可変長引数は最後に一つだけ
可変長は末尾に1つだけ定義できます。オーバーロードと併用すると解決が複雑になりがちなので、「必須+可変」のシンプルな形に留めるのが安全です。
static String join(String sep, String... parts) { /* ... */ } // 必須+可変
Javaオーバーロードの意味をそろえる
同名メソッドは「同じ目的の異バリエーション」に限定し、戻り値の意味と副作用の有無を一致させます。名前は一貫性を持たせ、シグネチャ間で振る舞いが変わらないようにします。
オーバーロードとオーバーライドの違い
まったく別の概念
- オーバーロード:同じクラス内で同名・異なる引数。コンパイル時に解決。
- オーバーライド:サブクラスが親クラスのメソッドを「同じシグネチャ」で差し替える。実行時に動的ディスパッチ。
class Base { void show(String s) { System.out.println("Base:" + s); } }
class Sub extends Base { @Override void show(String s) { System.out.println("Sub:" + s); } }
// オーバーロードとは別。ここは引数同じで「振る舞いを差し替え」。
Java両者の目的と解決タイミングを混同しないことが重要です。
実用例で身につける
例 1: 文字列連結のバリエーション
public class Joiner {
static String join(String a, String b) {
return a + b;
}
static String join(String a, String b, String c) {
return a + b + c;
}
static String join(String... parts) {
if (parts == null || parts.length == 0) return "";
StringBuilder sb = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) sb.append(parts[i]);
return sb.toString();
}
public static void main(String[] args) {
System.out.println(join("A", "B")); // AB
System.out.println(join("A", "B", "C")); // ABC
System.out.println(join("J", "A", "V", "A"));// JAVA
}
}
Java例 2: 数値合計(型ごとに最適化)
public class Sum {
static int sum(int a, int b) { return a + b; }
static long sum(long a, long b) { return a + b; }
static double sum(double a, double b) { return a + b; }
public static void main(String[] args) {
System.out.println(sum(3, 4)); // int
System.out.println(sum(3L, 4L)); // long
System.out.println(sum(2.5, 3.0)); // double
}
}
Java例 3: 入力源の違いを同名で受ける
import java.io.*;
public class ReaderUtil {
static String readAll(String s) { return s; }
static String readAll(File f) throws IOException {
try (var br = new BufferedReader(new FileReader(f))) {
var sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line).append('\n');
return sb.toString();
}
}
static String readAll(InputStream in) throws IOException {
return new String(in.readAllBytes());
}
}
Java「読み取り」という同一目的を、入力源の違いでオーバーロードするのは典型パターンです。
設計の指針(重要部分のまとめ)
- 同名メソッドは「同じ目的の異バリエーション」に限定し、戻り値と副作用の意味をそろえる。
- 解決ルール(型一致→拡張→ボクシング→可変長)を意識し、曖昧になる設計を避ける。必要なら明示キャストで意図を確定。
- 可変長引数は末尾に一つだけ。必須と任意を分けて、呼び出し側の意図が伝わるシグネチャに。
nullで曖昧になる参照型のオーバーロードは注意。契約(許容するか、キャストを要求するか)を明示。- オーバーロード(コンパイル時解決)とオーバーライド(実行時解決)を混同しない。目的とタイミングが違う。
