W3docs

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

Можно передавать:

  1. Свободные значенияf(1, 2, 3).
  2. Массив нужного типаf(new int[]{1, 2, 3}).
  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— editable, runs on the server

Что дальше

Теперь вы умеете писать методы с полным набором форм параметров, которые предлагает Java: фиксированными, перегруженными, рекурсивными и varargs. Заключительная глава части о методах возвращается к единственному методу, который есть в каждой программе: метод main — чья сигнатура String[] args может быть также записана как String... args — и что она на самом деле сообщает JVM.

Практика

Практика
В методе, объявленном как void f(int... xs), каков тип xs, если аргументы не переданы?
В методе, объявленном как void f(int... xs), каков тип xs, если аргументы не переданы?
Was this page helpful?