ООП ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-07-31

Varargs в Java: методы с переменным числом аргументов

Varargs (от англ. variable arguments) — это синтаксис Java, позволяющий объявить метод, который принимает переменное число аргументов. Такие методы ещё называют методами с аргументами переменной длины. Возможность появилась в Java 5 и с тех пор не менялась.

Для объявления параметра переменной длины используются три точки после типа:

static void test(int... array)

Вызывать такой метод можно с любым количеством аргументов — включая ноль:

test();          // 0 аргументов
test(1);         // 1 аргумент
test(1, 2, 3);   // 3 аргумента
test(new int[]{1, 2, 3}); // массив тоже подойдёт

Правила объявления varargs

Наряду с параметром переменной длины у метода могут быть и обычные параметры. Но параметр переменной длины должен быть последним в списке, и он может быть только один:

static void test(double d, int... array)   // корректно
// static void test(int... array, double d)          // ошибка компиляции
// static void test(int... a, double... b)           // ошибка компиляции
Правило Пример Результат
Три точки ставятся после типа параметра void test(int... a) Метод принимает 0 и более значений int
Varargs-параметр должен быть последним void test(String s, int... a) Корректно; обратный порядок не компилируется
Varargs-параметр в методе может быть только один void test(int... a, long... b) Ошибка компиляции
Внутри метода параметр — это массив a.length, a[0] Доступны все операции с массивом
Вызов без аргументов допустим test() Передаётся пустой массив, a.length == 0

Полезно знать

Точку входа тоже можно объявить через varargs: public static void main(String... args) — это полностью равнозначно записи String[] args, и JVM запустит такую программу без каких-либо оговорок.

Пример: метод с переменным числом аргументов

В примере ниже метод test() объявлен с переменным количеством аргументов типа int. Внутри метода мы обращаемся к параметру как к обычному массиву. При вызове можно передать любое количество значений — в том числе ноль, а также готовый массив:

public class VarArgsExample {
    static void test(int... array) {
        System.out.println("Количество аргументов: " + array.length);
        for (int a : array) {
            System.out.print(a + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        test();
        test(1);
        test(1, 2);
        test(new int[]{1, 3});
    }
}

Результат выполнения:

Количество аргументов: 0

Количество аргументов: 1
1
Количество аргументов: 2
1 2
Количество аргументов: 2
1 3

Varargs под капотом: это обычный массив

Varargs — это синтаксический сахар над массивами. Компилятор сам создаёт массив в точке вызова и подставляет его в качестве аргумента, поэтому следующие две строки после компиляции неразличимы:

test(1, 2, 3);
// компилятор превращает вызов выше в такой:
test(new int[]{1, 2, 3});

Из этого следуют два практических вывода:

  • внутри метода параметру доступны length, индексация, цикл for-each, Arrays.toString() и всё остальное, что применимо к массиву;
  • при вызове без аргументов приходит пустой массив, а не null, поэтому array.length безопасно вызывать всегда.

Важно

Каждый вызов varargs-метода создаёт новый массив в куче. В обычном коде это незаметно, но в горячих участках аллокации складываются. Именно поэтому в JDK у методов вроде List.of() есть отдельные перегрузки на 0–10 элементов, а varargs-вариант подключается только начиная с одиннадцатого аргумента.

Перегрузка методов с varargs

Методы с переменным числом аргументов участвуют в перегрузке по особым правилам. Компилятор подбирает метод в три фазы и переходит к следующей, только если на текущей не нашлось ни одного подходящего кандидата.

Фаза Что учитывается Что не учитывается Пример вызова
1. Строгое соответствие Точное совпадение и расширение примитивов Автоупаковка, varargs test(3)test(int a)
2. С автоупаковкой Autoboxing/unboxing Varargs test(3)test(Integer a)
3. С varargs Методы с переменным числом аргументов test(1, 2)test(int... a)

Отсюда главное правило: varargs-метод рассматривается последним. Если существует обычный метод, подходящий по сигнатуре, будет выбран именно он.

Если на третьей фазе подходят сразу несколько varargs-методов, компилятор выбирает наиболее специфичный — тот, чьи параметры можно передать в другой кандидат без потери информации. Для примера ниже test(int... array) специфичнее, чем test(double... array), потому что int расширяется до double, но не наоборот:

public class VarArgsExample2 {
    static void test(double... array) {
        System.out.println("test(double... array)");
        System.out.println("Количество аргументов: " + array.length);
        for (double a : array) {
            System.out.print(a + " ");
        }
        System.out.println();
    }

    static void test(int... array) {
        System.out.println("test(int... array)");
        System.out.println("Количество аргументов: " + array.length);
        for (int a : array) {
            System.out.print(a + " ");
        }
        System.out.println();
    }

    static void test(int a) {
        System.out.println("test(int a)");
    }

    public static void main(String[] args) {
        test();
        test(3);
        test(1.0);
        test(1, 2);
    }
}

Результат выполнения:

test(int... array)
Количество аргументов: 0

test(int a)
test(double... array)
Количество аргументов: 1
1.0
test(int... array)
Количество аргументов: 2
1 2

Разберём каждый вызов:

  • test() — обычные методы не подходят, работает фаза 3; из двух varargs-кандидатов выбран более специфичный test(int... array);
  • test(3) — на первой же фазе подходит test(int a), до varargs дело не доходит;
  • test(1.0)double нельзя сузить до int, поэтому остаётся только test(double... array);
  • test(1, 2) — двух аргументов не принимает ни один обычный метод, снова побеждает test(int... array).

Ошибка неоднозначности

При перегрузке методов с аргументами переменной длины легко получить неоднозначность: несколько кандидатов подходят одинаково хорошо, и ни один из них не является более специфичным. Такую ситуацию обнаруживает компилятор на этапе сборки — программа просто не соберётся, до запуска JVM дело не доходит.

В примере ниже метод test перегружен: один вариант принимает varargs типа boolean, второй — varargs типа int. Между boolean и int нет отношения расширения, поэтому при вызове test() без аргументов выбрать метод невозможно:

public class VarArgsExample3 {
    static void test(boolean... array) {
        System.out.println("test(boolean... array)");
        System.out.println("Количество аргументов: " + array.length);
        for (boolean a : array) {
            System.out.print(a + " ");
        }
        System.out.println();
    }

    static void test(int... array) {
        System.out.println("test(int... array)");
        System.out.println("Количество аргументов: " + array.length);
        for (int a : array) {
            System.out.print(a + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // test();  // ошибка компиляции: reference to test is ambiguous
        test(3);
        test(1, 2);
    }
}

Если раскомментировать вызов test(), компилятор выдаст:

error: reference to test is ambiguous
        both method test(boolean...) and method test(int...) match

Обойти неоднозначность можно двумя способами: передать явный пустой массив нужного типа — test(new int[0]) — или отказаться от одной из перегрузок и дать методам разные имена.

Varargs и дженерики: @SafeVarargs

Если varargs-параметр имеет обобщённый тип, компилятор предупреждает о возможном «загрязнении кучи» (heap pollution): из-за стирания типов массив List<String>[] на самом деле является List[], и в него можно положить что угодно.

static void unsafe(List<String>... lists) {   // предупреждение: possible heap pollution
    Object[] objects = lists;
    objects[0] = List.of(1, 2, 3);            // компилятор не возражает
    String s = lists[0].get(0);               // ClassCastException во время выполнения
}

Если метод только читает элементы и не записывает их в массив, предупреждение снимают аннотацией @SafeVarargs:

@SafeVarargs
static <T> List<T> toList(T... items) {
    return new ArrayList<>(Arrays.asList(items));
}

Где можно ставить @SafeVarargs

Аннотация допустима только для методов, которые нельзя переопределить: static, final, конструкторов, а начиная с Java 9 — ещё и private. На обычном методе экземпляра она вызовет ошибку компиляции, потому что наследник мог бы нарушить обещанную безопасность.

Varargs в стандартной библиотеке

Varargs используется в JDK повсеместно, и знакомые методы выглядят «магией» именно благодаря ему:

System.out.printf("%s = %d%n", "age", 30);      // printf(String, Object...)
String s = String.format("%s-%s", "a", "b");    // format(String, Object...)
List<String> list = List.of("a", "b", "c");     // of(E...)
List<Integer> nums = Arrays.asList(1, 2, 3);    // asList(T...)
int[] copy = {1, 2, 3};
System.out.println(Arrays.toString(copy));

На чём чаще всего ошибаются

  • Передача null напрямую. Вызов test(null) для метода test(String... s) передаёт не «один элемент null», а null вместо всего массива — следующий же s.length бросит NullPointerException. Чтобы передать один элемент, пишите test((String) null).
  • Примитивный массив вместо объектного. Arrays.asList(new int[]{1, 2, 3}) вернёт List<int[]> размером 1, а не список из трёх чисел: int[] — это один объект для varargs типа T. Нужен Integer[] или Arrays.stream(...).boxed().
  • Ожидание null при пустом вызове. Проверка if (array == null) бессмысленна, если метод вызывают обычным образом: приходит пустой массив.
  • Слишком «жадный» varargs. Метод test(Object... args) подходит почти под любой вызов и легко перехватывает те, которые вы планировали отдать другой перегрузке.
  • Varargs как замена отсутствующей валидации. Если аргументов всегда должно быть не меньше одного, объявляйте test(int first, int... rest) — тогда требование проверит компилятор, а не if в рантайме.

Часто задаваемые вопросы

Можно ли передать массив в varargs-метод?

Да. Метод test(int... array) принимает и test(1, 2, 3), и test(new int[]{1, 2, 3}) — компилятор в первом случае сам создаёт массив, так что оба вызова эквивалентны. Обратное неверно: метод, объявленный как test(int[] array), нельзя вызвать через test(1, 2, 3).

Что вернёт array.length, если вызвать метод без аргументов?

Ноль. Компилятор передаёт пустой массив, а не null, поэтому и array.length, и цикл for-each отработают корректно. null внутри метода может оказаться только в одном случае — если его передали явно, например test((int[]) null).

Почему varargs-параметр обязан быть последним?

Иначе компилятор не смог бы определить, где заканчивается список переменной длины и начинаются следующие параметры. По той же причине varargs-параметр в методе может быть только один. Всё остальное объявляется до него: void log(String prefix, Object... values).

Что быстрее: varargs или перегрузки с фиксированными параметрами?

Перегрузки с фиксированными параметрами: varargs создаёт новый массив на каждый вызов. Для прикладного кода разница несущественна, но в библиотеках и горячих циклах её учитывают — например, в JDK у List.of() есть отдельные перегрузки на количество элементов от нуля до десяти.

Зачем нужна аннотация @SafeVarargs?

Она подавляет предупреждение о загрязнении кучи для методов с обобщённым varargs-параметром вида T.... Ставить её можно только там, где метод нельзя переопределить: на статических и final-методах, конструкторах, а с Java 9 — и на private-методах. И только если метод действительно не записывает ничего в этот массив.

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.