Java | 基礎文法:可変長引数

Java Java
スポンサーリンク

可変長引数の全体像

可変長引数(varargs)は、メソッドに「0個以上の引数」を渡せる仕組みです。シグネチャでは Type... name と書き、呼び出し側は m()m(a)m(a, b, c) のように好きな個数で呼べます。実体は「配列」なので、メソッド内部では nameType[] として扱えます。重要なのは「最後の引数にしか置けない」「オーバーロードの曖昧さ」「配列扱いゆえの副作用(浅いコピーではない)」の3点です。


基本の使い方とシグネチャ

宣言と内部の見え方

static int sum(int... xs) {        // 可変長引数
    int total = 0;
    for (int x : xs) total += x;   // 実体は int[] 配列
    return total;
}

sum();             // 0(空配列)
sum(1);            // 1
sum(1, 2, 3);      // 6
Java
  • Type... は「最後の引数」にしか書けません。int... xs, String name のような並びは不可です。
  • メソッド内部では xs.lengthxs[0] のように配列としてアクセスします。

明示的に配列を渡す

呼び出し側で配列を渡すと、そのまま varargs に収まります。既存配列を使い回したいときに便利です。

int[] a = {1, 2, 3};
sum(a);            // 6(配列そのもの)
Java

便利な場面と実用例

文字列の連結や整形

static String join(String sep, String... parts) {
    return String.join(sep, parts);
}
System.out.println(join(", ", "A", "B", "C")); // A, B, C
Java

ログやメッセージのフォーマット(printf系)

System.out.printf(String format, Object... args) は典型例です。フォーマット文字列に対して、引数を好きな個数だけ渡します。

System.out.printf("id=%s, score=%d%n", "X-1", 99);
System.out.printf("no args%n");   // 引数なしでもOK
Java

小さなユーティリティ

static <T> boolean anyNull(T... xs) {
    for (T x : xs) if (x == null) return true;
    return false;
}
anyNull("A", null, "B"); // true
Java

重要ポイントの深掘り:設計とオーバーロード

末尾にしか置けない、1つだけ

可変長は「最後の引数で1つだけ」。複数の varargs は定義できません。

// NG: 複数 varargs
// void f(int... a, String... b);
Java

オーバーロードの曖昧さに注意

固定長と varargs を混在させると、解決が曖昧になりがちです。意図が明確に読み取れるように設計しましょう。

void print(String s) { /* ... */ }
void print(String... ss) { /* ... */ }

// print("A"); はどちら? → 固定長が優先されるが、読み手に負担。避けるべき。
Java

指針:

  • 固定長のオーバーロードと varargs を併置するなら、役割を分ける(メソッド名を変える)。
  • 最低引数数があるなら、別引数で受けてから「追加分」を varargs にする。
static String label(String head, String... tail) { /* head は必須、tail は0個以上 */ }
Java

パフォーマンスとアロケーション

呼び出しごとに配列が1つ作られます。高頻度・小さな呼び出しが大量にあるときは固定長オーバーロードやコレクション入力の方が効く場合があります。

// 高頻度なら
void add3(int a, int b, int c) { /* ... */ }
// または
void addAll(IntStream xs) { /* ... */ }
Java

null と配列の扱い(誤解しやすい点)

引数としての null と、要素としての null

  • 呼び出し側で「配列そのものを null」にすると NPE の原因になります(varargsは配列に展開される前提)。
  • 要素としての null は通常許容されます(ただしメソッド仕様次第)。
// NG: varargs に null の配列参照を渡す
int[] ref = null;
// sum(ref); // 実装によっては NullPointerException

// OK: 要素に null(参照型の場合)
static int countNonNull(String... xs) {
    int n = 0;
    for (String x : xs) if (x != null) n++;
    return n;
}
Java

プリミティブ vs ラッパー

int...Integer... は別物です。オートボクシングが発生するとコストが上がるため、数値中心ならプリミティブ側を選ぶのが無難です。


ジェネリクスと @SafeVarargs

非具象化型(reifiable でない)とヒープ汚染の注意

T... は実体が T[] ですが、型消去により実行時には Object[] 相当です。内部で配列に異なる型を混ぜるとヒープ汚染の危険があります。

static <T> void collect(java.util.List<T> out, T... xs) {
    for (T x : xs) out.add(x);
}
Java

安全に使う指針:

  • 受け取った varargs 配列を外へ公開しない(そのまま返さない)。
  • 配列に他型を挿入しない。
  • メソッドが「安全である」ことが明らかな場合、final/static なメソッドに @SafeVarargs を付けて警告を抑止します。
@SafeVarargs
static <T> java.util.List<T> listOf(T... xs) {
    return java.util.Arrays.asList(xs); // 配列は公開しない(リストのビューに留める)
}
Java

可変長引数の前処理と転送(forwarding)

受け取って別メソッドへ渡す

varargs を受けて、別の varargs へ「そのまま渡す」には配列のまま渡します。

static void debug(String tag, Object... args) {
    System.out.printf("[%s] ", tag);
    System.out.printf(java.util.Locale.JAPAN, "%s%n", java.util.Arrays.toString(args));
}

static void info(Object... args) {
    debug("INFO", args); // そのまま配列で転送
}
Java

先頭に必須引数、後ろは自由

必須の文脈(タグ、ヘッダなど)と、可変の本体を分けると設計が明快になります。

static void log(String level, Object... xs) { /* ... */ }
log("WARN", "id=", 123, "bad");  // level は必須、xs は0個以上
Java

例題で身につける

例 1: 文字列の結合(区切りあり・なし)

static String concat(String... parts) {
    return String.join("", parts);
}
static String concatWith(String sep, String... parts) {
    return String.join(sep, parts);
}
System.out.println(concat("A", "B", "C"));              // ABC
System.out.println(concatWith(", ", "A", "B", "C"));    // A, B, C
Java

例 2: スコアの平均(空は Optional.empty)

import java.util.OptionalDouble;

static OptionalDouble average(int... xs) {
    if (xs.length == 0) return OptionalDouble.empty();
    int sum = 0;
    for (int x : xs) sum += x;
    return OptionalDouble.of((double) sum / xs.length);
}
average();           // empty
average(10, 20, 30); // 20.0
Java

例 3: Comparator を作る(ジェネリクス+varargs)

@SafeVarargs
static <T> java.util.Comparator<T> chain(java.util.Comparator<T>... cs) {
    return (a, b) -> {
        for (var c : cs) {
            int r = c.compare(a, b);
            if (r != 0) return r;
        }
        return 0;
    };
}
var byLen = java.util.Comparator.comparingInt(String::length);
var byLex = java.util.Comparator.naturalOrder();
var cmp = chain(byLen, byLex); // 長さ→辞書順
Java

例 4: 必須+可変のログ

static void logf(String level, String format, Object... args) {
    System.out.printf("[%s] " + format + "%n", level, args);
}
logf("INFO", "id=%s, ok=%b", "X-1", true);
Java

仕上げのアドバイス(重要部分のまとめ)

可変長引数は「最後に1つだけ」「内部では配列」「呼び出し時に配列が1つ生成される」を軸に使う。オーバーロードは曖昧さを避け、必須引数+varargsの形にすると明快。nullは「配列参照のnull」を渡さない、要素のnullは仕様で扱う。数値はプリミティブでコストを抑え、ジェネリクスでは配列を公開せず安全に扱い、必要なら @SafeVarargs を付ける。転送は配列で渡し、printf系など既存APIの設計も参考にする——この型が身につけば、柔軟で読みやすいメソッド設計が自然に書けます。

タイトルとURLをコピーしました