Java Varargs (аргументы переменной длины)
Объявляйте методы Java с varargs (Type... name), принимающие любое число аргументов. Правила, соглашения вызова, перегрузка и типичные ошибки.
Параметр varargs (сокращение от variable-length arguments — аргументы переменной длины) позволяет методу принимать любое количество аргументов одного типа — ноль, один, десять, сто — без необходимости явно упаковывать их в массив. Последний параметр записывается как Type... name, и внутри метода name является обычным массивом этого типа.
Вы уже давно используете методы с varargs. String.format("%s + %s = %s", a, b, c) принимает столько значений подстановки, сколько нужно строке формата. List.of(1, 2, 3, 4, 5) принимает произвольное число элементов. Оба метода опираются на varargs.
Объявление параметра varargs
Синтаксис: три точки между типом элемента и именем параметра:
public static int sum(int... numbers) {
int total = 0;
for (int n : numbers) total += n;
return total;
}Вызывающий код передаёт любое количество аргументов типа int:
sum(); // 0
sum(5); // 5
sum(1, 2, 3); // 6
sum(1, 2, 3, 4); // 10Внутри метода numbers — это int[]. Компилятор упаковывает свободные аргументы в массив в точке вызова.
Varargs — это просто массив
Тело метода обращается с параметром varargs точно как с массивом — .length, индексирование, for-each и всё прочее:
public static String join(String sep, String... parts) {
if (parts.length == 0) return "";
StringBuilder sb = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) {
sb.append(sep).append(parts[i]);
}
return sb.toString();
}
join(", ", "red", "green", "blue"); // "red, green, blue"
join("-"); // ""Поскольку параметр является массивом, можно также передать литеральный массив напрямую:
String[] colors = {"red", "green", "blue"};
join(", ", colors); // same as join(", ", "red", "green", "blue")Компилятор принимает обе формы.
Правила
Синтаксис varargs сопровождается несколькими ограничениями:
- В методе может быть не более одного параметра varargs. Два параметра создали бы неоднозначность.
- Он должен быть последним параметром. Иначе компилятор не смог бы определить, где заканчиваются переменные аргументы и начинается следующий параметр.
- Фиксированные параметры идут первыми. Обычные параметры в начале, varargs — в конце.
// VALID
public static String tag(String name, String... attrs) { ... }
public static int sum(int initial, int... rest) { ... }
// INVALID
// public static int sum(int... a, int... b) // two varargs
// public static int sum(int... a, int b) // varargs not lastВызов методов с varargs
Можно передавать:
- Свободные значения —
f(1, 2, 3). - Массив нужного типа —
f(new int[]{1, 2, 3}). - Ничего вообще —
f(). Метод получит массив нулевой длины, а неnull.
sum(); // numbers is int[0]
sum(new int[0]); // also int[0]
sum(new int[]{1, 2, 3}); // numbers is int[]{1,2,3}Аргумент null — хитрый случай: sum(null) компилируется, трактует null как сам массив, а цикл выбрасывает NullPointerException. Большинство методов с varargs не ожидают null.
Varargs и перегрузка
При перегрузке метода версии с varargs проверяются последними. Перегрузка без varargs (фиксированной арности), которая подходит под вызов, будет предпочтена:
public static void show(int a, int b) { System.out.println("two ints"); }
public static void show(int... xs) { System.out.println("varargs"); }
show(1, 2); // "two ints" — fixed-arity wins
show(1); // "varargs" — no fixed match
show(1, 2, 3); // "varargs"Это правильно — varargs является запасным вариантом и используется только тогда, когда нет более конкретного совпадения.
Распространённая ошибка: Object...
Varargs из Object принимает что угодно, включая массив:
public static void log(Object... values) {
for (Object v : values) System.out.println(v);
}
log("a", 1, 2.5); // 3 calls
String[] names = {"Ada", "Bob"};
log(names); // ONE Object[] argument, prints "Ada" then "Bob"
log((Object) names); // ONE Object argument, prints "[Ljava.lang.String;@..."Компилятор выбирает ту интерпретацию, которая делает вызов корректным. С Object... обе интерпретации работают, и правила склоняются к «трактовать массив как массив varargs». Если нужно передать массив как единственный элемент, выполните приведение: log((Object) names).
Когда использовать varargs (и когда не стоит)
Varargs — удобство для вызывающего кода, а не абстракция без затрат. Каждый вызов, передающий свободные значения, выделяет новый массив. Для методов, вызываемых в плотных циклах на критическом пути, это накопление затрат может иметь значение — вот почему стандартная библиотека иногда предоставляет перегрузки с фиксированной арностью наряду с версией varargs. List.of — классический пример: у него есть специализированные перегрузки для of(), of(e1), of(e1, e2) вплоть до десяти элементов, и лишь после этого используется varargs of(E... elements).
Для повседневного кода стоимость пренебрежимо мала, и победа остаётся за читаемостью. Используйте varargs, когда:
- количество аргументов действительно варьируется (форматирование, логирование, создание коллекций);
- принуждение вызывающего писать
new Type[]{...}было бы лишним шумом.
Предпочтите обычный массив или параметр List, когда у вызывающего, как правило, уже есть коллекция, или когда хотите сделать «передачу многих значений» явным, ожидаемым случаем.
Пример в действии
Что дальше
Теперь вы умеете писать методы с полным набором форм параметров, которые предлагает Java: фиксированными, перегруженными, рекурсивными и varargs. Заключительная глава части о методах возвращается к единственному методу, который есть в каждой программе: метод main — чья сигнатура String[] args может быть также записана как String... args — и что она на самом деле сообщает JVM.