- JAVA
Java 可変長引数の使い方|String…の書き方と配列を渡すときの注意点
Javaの可変長引数とは、String... names のように型のあとへ ... を付けて、引数を0個以上いくつでも受け取れるようにする書き方です。 メソッドの中では names は普通の配列(String[])として扱えます。呼び出す側は show("A", "B", "C") のように値を並べるだけでよく、配列を自分で作る手間が省けます。
この記事では、引数0個・1個・複数・配列・null を渡したときに中身がどうなるかを、OpenJDK 21.0.10 で動かした出力つきの一覧表にまとめました。あわせて、int[] を Object... に渡すと1要素として扱われる動き、可変長引数は末尾に1つだけという制約のコンパイルエラー、固定引数版とのオーバーロード解決順、@SafeVarargs が必要になる場面を、再現コードとメッセージの原文で確認していきます。
可変長引数の早見表
まず「何を渡すと、メソッドの中でどう見えるか」です。受け取る側は static void show(String... names) とします。
| 呼び出し方 | names の中身 | names.length | 補足 |
|---|---|---|---|
show() |
[](空の配列) |
0 | null にはならず、長さ0の配列が渡る |
show("A") |
[A] |
1 | 1個でも配列に包まれる |
show("A", "B", "C") |
[A, B, C] |
3 | 並べた順に入る |
show(new String[] {"X", "Y"}) |
[X, Y] |
2 | 配列はそのまま渡る(包み直されない) |
show(new String[0]) |
[] |
0 | 引数なしと同じ見え方 |
show((String[]) null) |
null | (参照すると例外) | 配列そのものが null |
show((String) null) |
[null] |
1 | null を1要素として持つ配列 |
show(null, null) |
[null, null] |
2 | 2個以上並べれば要素として扱われる |
書き方のルールは次の表で用が足ります。
| やりたいこと | 書き方 | ポイント |
|---|---|---|
| 可変長引数を宣言する | void show(String... names) |
型の直後に ...。中では String[] |
| 固定の引数と組み合わせる | void log(String format, Object... args) |
可変長引数は末尾に1つだけ |
| 1個以上を必須にする | int max(int first, int... rest) |
先頭を固定引数にする |
| 配列を渡す | show(array) |
型が一致すればそのまま渡る |
| List を渡す | show(list.toArray(String[]::new)) |
配列に変換してから渡す |
| 配列を「1個の値」として渡す | objs((Object) array) |
Object にキャストする |
| ジェネリクスの警告を整理する | @SafeVarargs を付ける |
static・final・private のメソッドとコンストラクタに付けられる |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java 可変長引数の使い方|String...の書き方と配列を渡すときの注意点」
検証した環境とコードの動かし方
この記事の実行結果は、すべて次の環境で実際に動かして取得したものです。
| 項目 | 内容 |
|---|---|
| JDK | OpenJDK 21.0.10 |
| 実行方法 | java ファイル名.java(単一ファイルをそのまま実行) |
| コンパイルエラーの確認 | javac ファイル名.java |
| 警告の確認 | javac -Xlint:unchecked ファイル名.java など |
可変長引数は Java 5 から使える機能です。この記事のコードで Java 5 より後の機能を使っているのは List.of(Java 9 以降)や toList()(Java 16 以降)など周辺のAPIだけで、可変長引数そのものの動きはどのバージョンでも同じ考え方で読めます。なお、Java 22 以降での動作は今回は未実行です。
可変長引数の基本の書き方
型のあとに ... を付けるだけで、そのメソッドは引数をいくつでも受け取れるようになります。次のコードはそのまま VarargsBasic.java として保存して動かせます。
import java.util.Arrays;
public class VarargsBasic {
// 可変長引数:呼び出し側は String を0個以上並べられる
static void show(String... names) {
System.out.println("length=" + names.length + " 中身=" + Arrays.toString(names));
}
// int の合計を求める
static int sum(int... nums) {
int total = 0;
for (int n : nums) {
total += n;
}
return total;
}
public static void main(String[] args) {
show();
show("A");
show("A", "B", "C");
System.out.println(sum());
System.out.println(sum(1, 2, 3));
System.out.println(sum(new int[] {10, 20}));
}
}
実行結果(java VarargsBasic.java):
length=0 中身=[]
length=1 中身=[A]
length=3 中身=[A, B, C]
0
6
30
ここで押さえておきたい点は3つです。
- メソッドの中では配列です。
names.lengthで個数がわかり、拡張for文やnames[0]も使えます。 - 引数なしで呼ぶと、長さ0の配列が渡ります。null ではないので、ループはそのまま0回で終わります。
- 配列を1つ渡すこともできます。
sum(new int[] {10, 20})はsum(10, 20)と同じ結果になります。
可変長引数の正体は配列
可変長引数は、コンパイラが呼び出し側で配列を作ってくれる仕組みです。show("A", "B") と書くと、コンパイラは show(new String[] {"A", "B"}) と同じ内容に置き換えます。そのため、String... と String[] は、メソッドのシグネチャとしては同じものとして扱われます。同じクラスに両方を宣言すると、次のコンパイルエラーになります。
public class E2 {
static void f(String... a) { }
static void f(String[] a) { }
}
E2.java:3: error: cannot declare both f(String[]) and f(String...) in E2
static void f(String[] a) { }
^
1 error
同じ理由で、main メソッドは public static void main(String... args) と書いても起動できます。java MainV.java a b と実行して args.length を出力したところ、引数の数: 2 と表示されました。
| 比べる点 | String... names |
String[] names |
|---|---|---|
| メソッド内での型 | String[] |
String[] |
show("A", "B") と呼べる |
呼べる | 呼べない(配列を作って渡す) |
show() と呼べる |
呼べる(空の配列になる) | 呼べない |
show(array) と呼べる |
呼べる | 呼べる |
| 置ける位置 | 引数リストの末尾だけ | どこでも |
| 1メソッドに置ける数 | 1つ | いくつでも |
引数0個・複数・配列・nullを渡したときの中身を確かめる
冒頭の早見表を、実際のコードと出力で確認します。null のときに例外で止まらないよう、ここでは names == null の判定を入れています。
import java.util.Arrays;
public class VarargsDemo {
static void show(String label, String... names) {
if (names == null) {
System.out.println(label + " -> names は null");
} else {
System.out.println(label + " -> length=" + names.length
+ " 中身=" + Arrays.toString(names));
}
}
public static void main(String[] args) {
show("show()");
show("show(\"A\")", "A");
show("show(\"A\",\"B\",\"C\")", "A", "B", "C");
show("show(配列)", new String[] {"X", "Y"});
show("show(空の配列)", new String[0]);
show("show((String[]) null)", (String[]) null);
show("show((String) null)", (String) null);
show("show(null, null)", null, null);
}
}
実行結果:
show() -> length=0 中身=[]
show("A") -> length=1 中身=[A]
show("A","B","C") -> length=3 中身=[A, B, C]
show(配列) -> length=2 中身=[X, Y]
show(空の配列) -> length=0 中身=[]
show((String[]) null) -> names は null
show((String) null) -> length=1 中身=[null]
show(null, null) -> length=2 中身=[null, null]
null は「何にキャストするか」で意味が変わります。 (String[]) null は「配列がない」、(String) null は「null という要素が1つある」です。この違いが、次の節で扱う警告と例外につながります。
キャストなしで null を1つだけ渡すとどうなるか
show(null) のように、キャストなしの null を可変長引数の位置に1つだけ渡すと、コンパイルは通りますが警告が出ます。そして null は配列そのものとして渡ります。
public class E4 {
static void show(String... names) {
System.out.println(names.length);
}
public static void main(String[] args) {
show(null);
}
}
javac -Xlint:all E4.java の出力:
E4.java:6: warning: non-varargs call of varargs method with inexact argument type for last parameter;
show(null);
^
cast to String for a varargs call
cast to String[] for a non-varargs call and to suppress this warning
1 warning
実行すると、names.length を読むところで次の例外になります。
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "<parameter1>" is null
at E4.show(E4.java:3)
at E4.main(E4.java:6)
警告文がそのまま直し方を教えてくれています。
| 意図 | 書き方 | names の中身 |
|---|---|---|
| null を1個の要素として渡したい | show((String) null) |
[null] |
| 配列がないことを渡したい | show((String[]) null) |
null |
| 何も渡さない | show() |
[] |
受け取る側でできる備えとしては、次の2つが扱いやすい方法です。
static void show(String... names) {
// 方法1:配列が null なら空の配列として扱う
String[] safe = (names == null) ? new String[0] : names;
// 方法2:null を受け付けないと決めて、早めに知らせる
java.util.Objects.requireNonNull(names, "names は null にできません");
}
配列を渡すときの注意点
型が同じ配列はそのまま渡り、コピーされない
String... に String[] を渡したとき、コンパイラは新しい配列を作りません。呼び出し元の配列がそのまま渡るので、メソッドの中で要素を書き換えると、呼び出し元の配列も変わります。
import java.util.Arrays;
public class Alias {
static void fill(String... names) {
if (names.length > 0) {
names[0] = "書き換え";
}
}
public static void main(String[] args) {
String[] arr = {"A", "B"};
fill(arr);
System.out.println(Arrays.toString(arr));
}
}
実行結果:
[書き換え, B]
受け取った配列をフィールドに保存するときや、中身を並べ替えたいときは、names.clone() や List.copyOf(Arrays.asList(names)) でコピーを取ってから使うと、呼び出し元と互いに影響しなくなります。
List や Stream から渡すときは配列に変換する
可変長引数が受け取れるのは「並べた値」か「配列」です。List をそのまま渡すと、String... ではコンパイルエラーになり、Object... では List 全体が1要素になります。toArray で配列にしてから渡します。
List<String> list = List.of("x", "y", "z");
show(list.toArray(new String[0])); // どのバージョンでも使える書き方
show(list.toArray(String[]::new)); // Java 11 以降
show(Stream.of("p", "q").toArray(String[]::new)); // Stream から
show を length と中身 を出す形にして動かした結果:
3 [x, y, z]
3 [x, y, z]
2 [p, q]
可変長引数のメソッドから、別の可変長引数のメソッドへ受け渡すこともできます。static void wrap(String... names) { show(names); } として wrap("m", "n") を呼ぶと、2 [m, n] と表示されました。受け取った配列をそのまま次へ渡せば、要素は展開された状態で伝わります。
「値を並べる」と「配列を渡す」は混ぜられない
show("A", arr) のように、値と配列を混ぜて渡すことはできません(String と String[] は別の型のため、型が合わないというコンパイルエラーになります)。先頭に1つ足したいときは、配列を作り直します。
String[] arr = {"B", "C"};
String[] all = Stream.concat(Stream.of("A"), Arrays.stream(arr)).toArray(String[]::new);
show(all);
int[]をObject...に渡すと1要素になる理由と直し方
配列を渡すときにいちばん戸惑いやすいのが、基本型の配列(int[] など)を Object... や T... に渡したときです。
import java.util.Arrays;
import java.util.List;
public class ObjectVarargs {
static void objs(Object... args) {
String type = (args.length > 0 && args[0] != null)
? args[0].getClass().getSimpleName() : "-";
System.out.println("length=" + args.length + " 先頭の型=" + type);
}
public static void main(String[] args) {
objs(new int[] {1, 2, 3}); // 基本型の配列
objs(new Integer[] {1, 2, 3}); // ラッパー型の配列
objs(new String[] {"a", "b"}); // String の配列
objs((Object) new String[] {"a", "b"}); // Object にキャスト
objs(1, 2, 3); // 値を並べる
System.out.println(Arrays.asList(new int[] {1, 2, 3}).size());
System.out.println(Arrays.asList(new Integer[] {1, 2, 3}).size());
System.out.println(List.of(new int[] {1, 2, 3}).size());
}
}
実行結果:
length=1 先頭の型=int[]
length=3 先頭の型=Integer
length=2 先頭の型=String
length=1 先頭の型=String[]
length=3 先頭の型=Integer
1
3
1
| 渡したもの | 受け取り側 Object... args |
理由 |
|---|---|---|
new int[] {1, 2, 3} |
長さ1(要素は int[]) |
int[] は Object[] ではないので、配列全体が1個の Object として包まれる |
new Integer[] {1, 2, 3} |
長さ3 | Integer[] は Object[] として扱えるので、そのまま渡る |
new String[] {"a", "b"} |
長さ2 | String[] も Object[] として扱える |
(Object) new String[] {...} |
長さ1(要素は String[]) |
「1個の値」と明示したので包まれる |
1, 2, 3 |
長さ3 | 1つずつ Integer に変換されて配列になる |
int は Object ではないため、int[] を Object[] として見ることができません。 そこでコンパイラは「int[] という1個のオブジェクトが渡された」と解釈し、長さ1の Object[] に包みます。Arrays.asList(new int[] {1, 2, 3}).size() が 1 になるのも同じ理由で、できあがるのは List<int[]> です。
直し方は次のとおりです。
| やりたいこと | 書き方 | 動かした結果 |
|---|---|---|
int[] の要素を1つずつ渡す |
objs(Arrays.stream(nums).boxed().toArray()) |
length は 3 |
int[] を書式に流し込む |
String.format("%d-%d-%d", Arrays.stream(nums).boxed().toArray()) |
1-2-3 |
int[] から List を作る |
Arrays.stream(nums).boxed().toList() |
List<Integer> になる |
| 最初からラッパー型の配列にする | Integer[] nums = {1, 2, 3}; |
そのまま3要素で渡る |
参照型の配列をObject...に渡すと出る警告
String[] や Integer[] を Object... に渡すと、動作は「要素を展開して渡す」になりますが、コンパイラは意図を確認する警告を出します。
VarargsDemo.java:50: warning: non-varargs call of varargs method with inexact argument type for last parameter;
objs(new String[] {"a", "b"});
^
cast to Object for a varargs call
cast to Object[] for a non-varargs call and to suppress this warning
「要素を展開したい」なら (Object[]) array、「配列を1個の値として渡したい」なら (Object) array とキャストすれば、意図がコードに残り、警告も出なくなります。String.format("%s %s", new String[] {"a", "b"}) は a b と表示されましたが、同じ警告が出ます。String.format("%s %s", (Object[]) arr) と書くと読み手にも伝わりやすくなります。
可変長引数は末尾に1つだけという制約
可変長引数は、引数リストの最後に、1つだけ置けます。途中に置いたり、2つ置いたりすると、どこまでが可変長の部分かをコンパイラが決められなくなるためです。
public class E1 {
static void a(String... names, int count) { }
static void b(String... x, int... y) { }
}
javac E1.java の出力:
E1.java:2: error: varargs parameter must be the last parameter
static void a(String... names, int count) { }
^
E1.java:3: error: varargs parameter must be the last parameter
static void b(String... x, int... y) { }
^
2 errors
直し方は、固定の引数を前に寄せるか、片方を配列や List に変えることです。
| 直す前 | 直したあと | 考え方 |
|---|---|---|
a(String... names, int count) |
a(int count, String... names) |
固定の引数を前に置く |
b(String... x, int... y) |
b(String[] x, int... y) |
片方を配列にする |
b(String... x, int... y) |
b(List<String> x, int... y) |
片方を List にする |
1個以上を必須にしたいときの書き方
int... nums だけだと、引数0個でも呼べてしまいます。最低1個は必要なメソッドでは、先頭を固定引数にします。 こうすると、0個で呼んだときにコンパイルの段階で気づけます。
static int max(int first, int... rest) {
int m = first;
for (int n : rest) {
m = Math.max(m, n);
}
return m;
}
max(3) は 3、max(3, 9, 5) は 9 を返しました。max() は引数が足りないため、コンパイルエラーになります。
固定引数版とのオーバーロード解決順
同じ名前で「固定引数版」と「可変長引数版」の両方があるとき、どちらが呼ばれるかには決まった順番があります。コンパイラは、可変長引数を使わなくても呼べるメソッドを先に探し、見つからなかったときに初めて可変長引数版を候補にします。
flowchart TD
A[呼び出しを見つける] --> B{第1段階: ボクシングも可変長も使わずに呼べるか}
B -- 呼べる --> X[そのメソッドに決定]
B -- 呼べない --> C{第2段階: ボクシングを使えば呼べるか}
C -- 呼べる --> X
C -- 呼べない --> D{第3段階: 可変長引数として呼べるか}
D -- 呼べる --> X
D -- 呼べない --> E[コンパイルエラー]
ボクシングとは、int を Integer に自動で変換するような、基本型とラッパー型の間の変換のことです。次のコードで順番を確かめます。
import java.util.Arrays;
public class Overload {
static void log(String msg) { System.out.println("固定1個版: " + msg); }
static void log(String msg, String arg) { System.out.println("固定2個版: " + msg + ", " + arg); }
static void log(String... msgs) { System.out.println("可変長版: " + Arrays.toString(msgs)); }
static void num(long x) { System.out.println("long版(型の拡大)"); }
static void num(Integer x) { System.out.println("Integer版(ボクシング)"); }
static void num(int... x) { System.out.println("int...版(可変長)"); }
static void num2(Integer x) { System.out.println("Integer版(ボクシング)"); }
static void num2(int... x) { System.out.println("int...版(可変長)"); }
public static void main(String[] args) {
log("a");
log("a", "b");
log("a", "b", "c");
log();
num(1);
num2(1);
num2();
num2(1, 2);
}
}
実行結果:
固定1個版: a
固定2個版: a, b
可変長版: [a, b, c]
可変長版: []
long版(型の拡大)
Integer版(ボクシング)
int...版(可変長)
int...版(可変長)
| 呼び出し | 選ばれたメソッド | 決まった段階 |
|---|---|---|
log("a") |
固定1個版 | 第1段階(そのまま呼べる) |
log("a", "b") |
固定2個版 | 第1段階 |
log("a", "b", "c") |
可変長版 | 第3段階(固定版に合うものがない) |
log() |
可変長版 | 第3段階 |
num(1) |
long 版 |
第1段階(int から long への拡大) |
num2(1) |
Integer 版 |
第2段階(ボクシング) |
num2() / num2(1, 2) |
int... 版 |
第3段階 |
int の値 1 を渡したとき、「long への拡大」→「Integer へのボクシング」→「int...」の順で優先されることが出力からわかります。見た目では int... がいちばん近く感じられますが、可変長引数版は最後の候補です。
この仕組みは、よく呼ばれるメソッドで「引数1個版・2個版・可変長版」を並べて用意する書き方の土台にもなっています。固定引数版が選ばれた呼び出しでは、配列を作る処理が発生しません。
可変長引数どうしのオーバーロードで起きる「ambiguous」
可変長引数版を複数用意すると、コンパイラがどちらにも決められない場面が出てきます。
public class E3 {
static void f(String... a) { System.out.println("String..."); }
static void f(Integer... a) { System.out.println("Integer..."); }
public static void main(String[] args) {
f("a"); // これは String... に決まる
f(); // どちらにも当てはまる
f(null); // どちらにも当てはまる
}
}
E3.java:6: error: reference to f is ambiguous
f();
^
both method f(String...) in E3 and method f(Integer...) in E3 match
E3.java:7: error: reference to f is ambiguous
f(null);
^
both method f(String...) in E3 and method f(Integer...) in E3 match
int... と Integer... の組み合わせでも、g(1, 2) が同じエラーになりました。
E5.java:9: error: reference to g is ambiguous
g(1, 2);
^
both method g(int...) in E5 and method g(Integer...) in E5 match
一方、f(int...) と f(long...) の組み合わせでは、f(1, 2) と f() は int... 版、f(1L) は long... 版が選ばれました。型に上下関係があるとより具体的なほうに決まり、String と Integer、int と Integer のように上下関係がない組み合わせでは決められない、という整理になります。
| 状況 | 起きること | 整え方 |
|---|---|---|
f(String...) と f(Integer...) に f() |
ambiguous のコンパイルエラー | メソッド名を分ける(fNames / fIds) |
g(int...) と g(Integer...) に g(1, 2) |
ambiguous のコンパイルエラー | どちらか片方だけにする |
f(int...) と f(long...) に f(1, 2) |
int... 版が選ばれる |
意図どおりか出力で確かめる |
可変長引数のメソッドは、オーバーロードを増やすより名前で区別するほうが、呼び出し側が迷わず読めます。
@SafeVarargsが必要になる場面
ジェネリクスの型(T... や List<String>...)を可変長引数にすると、コンパイラが警告を出します。Javaではジェネリクスの型を要素にした配列を安全に作れないため、「この配列に想定外の型が入り込む可能性がある」ことを知らせる警告です。この状態を ヒープ汚染(heap pollution) と呼びます。
import java.util.ArrayList;
import java.util.List;
public class Safe {
static <T> List<T> listOf(T... items) {
List<T> list = new ArrayList<>();
for (T item : items) list.add(item);
return list;
}
@SafeVarargs
static <T> List<T> safeListOf(T... items) {
List<T> list = new ArrayList<>();
for (T item : items) list.add(item);
return list;
}
public static void main(String[] args) {
List<List<String>> a = listOf(List.of("a"), List.of("b"));
List<List<String>> b = safeListOf(List.of("a"), List.of("b"));
System.out.println(a + " " + b);
}
}
javac -Xlint:unchecked Safe.java の出力:
Safe.java:5: warning: [unchecked] Possible heap pollution from parameterized vararg type T
static <T> List<T> listOf(T... items) {
^
where T is a type-variable:
T extends Object declared in method <T>listOf(T...)
Safe.java:17: warning: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
List<List<String>> a = listOf(List.of("a"), List.of("b"));
^
2 warnings
警告は 宣言側(5行目)と呼び出し側(17行目)の両方に出ています。@SafeVarargs を付けた safeListOf のほうには、どちらの警告も出ていません。実行結果は [[a], [b]] [[a], [b]] で、動きは同じです。
@SafeVarargs は「このメソッドは受け取った配列を安全に使っています」と作者が約束する印です。次の2点を満たしているときに付けます。
| 確認すること | 満たしている例 | 満たしていない例 |
|---|---|---|
| 配列に何も書き込まない | 読み取ってループするだけ | items[0] = 別の値; |
| 配列をメソッドの外へ出さない | 要素を List に移して返す | return items;、フィールドに保存 |
@SafeVarargsを付けられる場所
@SafeVarargs は、オーバーライドされないメソッドにだけ付けられます。普通のインスタンスメソッドに付けると、次のコンパイルエラーになります。
public class Safe2 {
@SafeVarargs
<T> void inst(T... items) { }
}
Safe2.java:4: error: Invalid SafeVarargs annotation. Instance method <T>inst(T...) is neither final nor private.
<T> void inst(T... items) { }
^
| 付ける場所 | 付けられるか |
|---|---|
| static メソッド | 付けられる |
| final のインスタンスメソッド | 付けられる |
| private のインスタンスメソッド | 付けられる(Java 9 以降) |
| コンストラクタ | 付けられる |
| 上記以外のインスタンスメソッド | コンパイルエラー(上の出力) |
約束を守らないとどうなるかを再現する
@SafeVarargs を付けたうえで配列に書き込むと、警告は出ないまま、実行時に離れた場所で例外になります。
import java.util.List;
public class Heap {
@SafeVarargs
static void broken(List<String>... lists) {
Object[] objs = lists; // 配列は Object[] としても見える
objs[0] = List.of(42); // List<Integer> を入れてしまう
String s = lists[0].get(0); // ここで型が合わなくなる
System.out.println(s);
}
public static void main(String[] args) {
broken(List.of("a"));
}
}
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at Heap.broken(Heap.java:8)
書き込んだ7行目ではなく、取り出した8行目で例外になっている点がポイントです。原因と症状の場所が離れるため、@SafeVarargs は「読み取るだけ」のメソッドに限って付けるのが安心です。標準ライブラリの List.of(E...) や Arrays.asList(T...) も、同じ考え方でこの注釈が付けられているメソッドです。
標準APIで出会う可変長引数
可変長引数は、普段使っている標準APIにも多く使われています。今回動かして確かめたものを表にしました。
| メソッド | 引数の形 | 呼び出し例 | 動かした結果 |
|---|---|---|---|
String.format |
String, Object... |
String.format("%d-%d-%d", 1, 2, 3) と同じ形 |
1-2-3 |
String.join |
CharSequence, CharSequence... |
String.join(",", "a", "b") |
a,b |
Arrays.asList |
T... |
Arrays.asList(1, 2, 3) |
[1, 2, 3] |
List.of |
E...(固定個数版もある) |
List.of(new int[] {1, 2, 3}).size() |
1 |
Collections.addAll |
Collection, T... |
Collections.addAll(target, "a", "b") |
[a, b] |
EnumSet.of |
E, E... |
EnumSet.of(MONDAY, FRIDAY) |
[MONDAY, FRIDAY] |
Path.of |
String, String... |
Path.of("data", "2026", "a.txt") |
data/2026/a.txt |
Stream.of |
T... |
Stream.of("p", "q") |
2要素の Stream |
EnumSet.of(E first, E... rest) や Path.of(String first, String... more) は、前の節で紹介した 「先頭を固定引数にして1個以上を必須にする」形になっています。自分でメソッドを設計するときの手本として参考になります。
可変長引数・配列・Listの使い分け
引数を「いくつでも」受け取りたいとき、選べる形は3つあります。
| 観点 | 可変長引数 T... |
配列 T[] |
List<T> |
|---|---|---|---|
| 呼び出し側の書きやすさ | 値を並べるだけ | new T[] {...} が必要 |
List.of(...) が必要 |
| 0個で呼ぶ | f() |
f(new T[0]) |
f(List.of()) |
| すでにコレクションを持っている | toArray が必要 |
toArray が必要 |
そのまま渡せる |
| 置ける位置・個数 | 末尾に1つ | 制限なし | 制限なし |
| ジェネリクスとの相性 | 警告が出る(@SafeVarargs で整理) |
配列を作りにくい | 相性がよい |
| 向いている場面 | 呼び出し側がその場で値を並べる | 基本型の配列を扱う処理 | 件数が多い、加工して受け渡す |
判断の目安は次のとおりです。
- 呼び出し側が、その場で2〜3個の値を並べることが多い → 可変長引数
- 呼び出し側が、すでに List や Set を持っていることが多い →
List<T>やCollection<T> - 両方に応えたい →
Collection<T>版を本体にして、可変長引数版はList.of(items)やArrays.asList(items)で本体へ渡す薄いメソッドにする
static int total(java.util.Collection<Integer> nums) {
int t = 0;
for (int n : nums) t += n;
return t;
}
static int total(Integer... nums) {
return total(java.util.Arrays.asList(nums));
}
つまずきやすい点のチェックリスト
| 症状・メッセージ | 原因 | 整え方 |
|---|---|---|
varargs parameter must be the last parameter |
可変長引数が末尾にない、または2つある | 固定引数を前に寄せる。片方を配列か List にする |
cannot declare both f(String[]) and f(String...) |
T... と T[] は同じシグネチャ |
どちらか一方にする |
reference to f is ambiguous |
可変長引数版が複数あり、どちらにも当てはまる | メソッド名を分ける |
non-varargs call of varargs method with inexact argument type |
null や配列の渡し方が2通りに読める | (Object) か (Object[])、(String) か (String[]) にキャスト |
Cannot read the array length because ... is null |
配列そのものとして null が渡った | 呼び出し側でキャスト、受け取り側で null を判定 |
| 要素数が 1 になる | int[] を Object... や T... に渡した |
boxed().toArray() で変換、またはラッパー型の配列にする |
Possible heap pollution from parameterized vararg type |
ジェネリクスの可変長引数 | 読み取り専用なら @SafeVarargs を付ける |
Invalid SafeVarargs annotation |
オーバーライドできるメソッドに付けた | static・final・private のいずれかにする |
| 呼び出し元の配列が書き換わった | 配列がコピーされずに渡っている | メソッド内で clone() してから変更する |
よくある質問
Q. 可変長引数に何も渡さないと null になりますか?
A. なりません。show() と呼ぶと、長さ0の配列が渡ります。今回の実行でも length=0 中身=[] と表示されました。null になるのは、show((String[]) null) と書いたときや、キャストなしで show(null) と書いたときです。
Q. String... と String[] はどちらを使えばよいですか?
A. メソッドの中での扱いは同じ配列です。違いは呼び出し側の書き方で、値をその場で並べて呼ぶことが多いなら String...、すでに配列や List を持っている相手から呼ばれることが多いなら配列や List<String> が合います。両方を同じクラスに宣言することはできません。
Q. 配列を渡したのに、要素数が1になりました。なぜですか?
A. int[] などの基本型の配列を Object... や T... に渡すと、配列全体が1個の値として包まれます。int が Object ではないためです。Arrays.stream(nums).boxed().toArray() で Integer の配列に変換するか、最初から Integer[] で持つと、要素が1つずつ渡ります。
Q. 可変長引数は処理が遅くなりますか? A. 可変長引数として呼ぶたびに、呼び出し側で配列が1つ作られます。多くの処理では気にならない大きさですが、とても頻繁に呼ばれるメソッドでは、引数1個版・2個版の固定引数メソッドを並べて用意する書き方があります。固定引数版が先に選ばれることは、この記事のオーバーロードの出力で確認したとおりです。なお、速度の計測は今回は行っていません(未実行)。
まとめ
型... 名前と書くと、0個以上の引数を配列として受け取れる。 メソッドの中では普通の配列として扱う。- 引数なしで呼ぶと長さ0の配列になり、null にはならない。 null を渡すときは
(String)か(String[])にキャストして意図を示す。 - 型が同じ配列はそのまま渡り、コピーされない。 中で書き換えると呼び出し元も変わるので、変更するときは
clone()する。 int[]をObject...に渡すと1要素になる。boxed().toArray()で変換するか、ラッパー型の配列を使う。- 可変長引数は末尾に1つだけ。 1個以上を必須にしたいときは
first, rest...の形にする。 - オーバーロードでは固定引数版が先に選ばれ、可変長引数版は最後の候補。 可変長引数版を複数並べると ambiguous になりやすいので、名前で分ける。
- ジェネリクスの可変長引数には、読み取り専用であることを確かめたうえで
@SafeVarargsを付ける。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java 可変長引数の使い方|String...の書き方と配列を渡すときの注意点」
株式会社RJCでは、Javaの可変長引数のような基本文法の細かな動きを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。