JavaRush /Курсы /Kotlin SELF /vararg в сигнатуре: зачем нужен и как выглядит вызов

vararg в сигнатуре: зачем нужен и как выглядит вызов

Kotlin SELF
16 уровень , 0 лекция
Открыта

1. Зачем нужен vararg

Иногда в программировании случается очень бытовая ситуация: вы хотите написать функцию, которая делает одно и то же действие для набора значений, но заранее не знаете, сколько значений будет. Сегодня их 2, завтра 5, послезавтра 0 (да, такое тоже бывает, и это не всегда ошибка).

Можно, конечно, заставить вызывающий код вручную собирать массив, можно передавать “пустые значения” вроде 0 или "" (и потом страдать), можно писать разные функции “на 2 аргумента”, “на 3 аргумента”… но это быстро превращается в цирк.

Для таких случаев в Kotlin есть параметр с переменным числом аргументов — vararg.

Что такое vararg

Когда вы видите в сигнатуре функции слово vararg, это означает: “в эту функцию можно передать 0 или больше аргументов этого типа”. То есть диапазон количества аргументов выглядит как 0..N.

Формально это часть языка Kotlin: один параметр в сигнатуре помечается модификатором vararg, и Kotlin разрешает вызывать функцию, перечисляя любое количество значений. Внутри функции этот параметр ведёт себя как массив — к этому мы подойдём чуть дальше.

Начнём с самого “классического” примера — суммы.

2. Синтаксис и базовые вызовы vararg

Минимальный пример: суммируем сколько угодно чисел

Сейчас будет код, который выглядит слишком просто — и это нормально. Наша цель: чтобы вы увидели, как выглядит vararg и как выглядит вызов.

fun sumAll(vararg xs: Int): Int {
    var sum = 0
    for (x in xs) sum += x
    return sum
}

fun main() {
    println(sumAll(1, 2, 3))        // 6
    println(sumAll(10, 20))         // 30
}

Обратите внимание на две вещи. Во-первых, vararg xs: Int стоит на месте обычного параметра — это всё ещё “параметр функции”, просто особенный. Во-вторых, вызов sumAll(1, 2, 3) выглядит как обычный вызов функции, где вы перечислили аргументы через запятую.

Вызов без аргументов: vararg допускает f()

С vararg почти всегда происходит маленький “момент истины”: вы внезапно понимаете, что функция легально вызывается вообще без аргументов. Это не ошибка компиляции, Kotlin не будет ругаться. И вот тут вы, как автор функции, обязаны задуматься: “А что моя функция должна делать в таком случае?”

Продолжим пример суммы — для суммы “пустого набора” логично вернуть 0.

fun sumAll(vararg xs: Int): Int {
    var sum = 0
    for (x in xs) sum += x
    return sum
}

fun main() {
    println(sumAll())               // 0
    println(sumAll(5))              // 5
}

В этом примере sumAll() возвращает 0 не потому, что Kotlin “сам так решил”, а потому, что мы написали код, который при пустом наборе просто не зайдёт в цикл и вернёт начальное значение sum.

Как vararg выглядит в сигнатуре и как это читать

На этом этапе важно научиться “читать глазами” сигнатуру. Когда вы видите:

fun printLines(vararg lines: String) { ... }

это читается примерно так: “printLines принимает любое количество строк, и внутри мы обработаем их по очереди”.

В Kotlin обычно vararg ставят ближе к концу списка параметров (часто вообще последним), потому что так вызовы выглядят естественнее.

Давайте сделаем “прикладную” функцию печати:

fun printLines(vararg lines: String) {
    for (line in lines) {
        println(line)
    }
}

fun main() {
    printLines("one", "two", "three")
    // one
    // two
    // three
}

И вот здесь vararg реально начинает ощущаться как удобство: вместо того чтобы вручную создавать массив строк “ради одной печати”, вы просто перечисляете значения.

3. Как vararg работает внутри функции

Что происходит “под капотом”: vararg превращается в массив

Сейчас будет важная мысль, которая сильно помогает не путаться. Когда вы вызываете sumAll(1, 2, 3), Kotlin внутри компилятора делает примерно такую штуку: “упаковывает” эти числа в массив и передаёт его в функцию.

Для нас на практике это означает: внутри функции вам доступны привычные вещи из темы массивов — цикл for (x in xs), размер xs.size, обращение по индексу xs[i].

Для визуальности можно представить процесс так:

flowchart TD
    A["Вызов: sumAll(1, 2, 3)"] --> B["Компилятор упаковал в набор"]
    B --> C["Внутри функции xs ведёт себя как массив"]
    C --> D["Цикл for (x in xs) считает сумму"]

Не нужно запоминать, как именно оно хранится в памяти — достаточно помнить практическое правило: внутри функции vararg можно перебирать как массив.

vararg внутри функции: size, индексы и печать

Когда вы чуть освоились с for (x in xs), почти сразу хочется сделать что-то более умное: например, вывести элементы с номерами или понять, сколько аргументов пришло. И это нормальное желание: вы начинаете думать как разработчик, а не как человек, который просто “копирует пример”.

Сделаем функцию, которая печатает числа и их количество:

fun printNumbers(vararg xs: Int) {
    println("Count = ${xs.size}")
    for (i in xs.indices) {
        println("xs[$i] = ${xs[i]}")
    }
}

fun main() {
    printNumbers(7, 8, 9)
    // Count = 3
    // xs[0] = 7
    // xs[1] = 8
    // xs[2] = 9
}

Здесь вы видите связку из прошлых тем: indices у массивов и индексация xs[i]. Это ровно тот же стиль, что был на теме про массивы, просто теперь массив “прилетает” в функцию через vararg.

Ограничение: в одной функции может быть только один vararg

В Kotlin есть важное правило: только один параметр функции может быть помечен как vararg.

Это не “придирка” языка, а способ избежать неоднозначностей: если бы можно было написать fun f(vararg a: Int, vararg b: Int), то при вызове f(1, 2, 3) было бы непонятно, где заканчивается a и где начинается b.

Вот пример, который не скомпилируется:

fun broken(vararg a: Int, vararg b: Int) { // так нельзя
    println(a.size + b.size)
}

Компилятор не даст вам это собрать — и тем самым спасёт от кода, который невозможно нормально вызывать.

4. Практика: vararg в «Угадай число»

Зачем vararg нужен в проекте

vararg — это не “прикольная фича языка”, а инструмент, который делает код удобнее и чище, особенно когда вы печатаете много сообщений или собираете данные в одном месте.

Представим, что в вашем мини‑проекте «Угадай число» вы часто делаете такие куски:

println("Welcome!")
println("I picked a number.")
println("Try to guess it.")

Это работает, но выглядит шумно: много строк печати, логика игры тонет в “болтовне”.

Сделаем маленькую утилиту: “выведи несколько строк”. И вот тут vararg идеально подходит: количество строк каждый раз разное.

fun say(vararg lines: String) {
    for (line in lines) {
        println(line)
    }
}

Теперь в main или в вашем игровом цикле можно писать компактнее и читабельнее.

Пример: приветствие игры через say(vararg ...)

Важно: примеры кода будут короткими, но логически связаны. Представьте, что это фрагменты одного файла GuessNumber.kt, который вы уже делали ранее.

fun say(vararg lines: String) {
    for (line in lines) println(line)
}

fun main() {
    say(
        "Добро пожаловать в игру!",
        "Я загадал число.",
        "Попробуйте угадать :)"
    )
}

Обратите внимание: с точки зрения синтаксиса это обычный вызов функции. Просто аргументов много. Kotlin не требует никаких скобок, массивов, дополнительных действий. Вы перечисляете строки, а функция получает их как “набор”.

Пример: сообщения-подсказки как vararg

В «Угадай число» часто бывает ветвление: мы печатаем разные подсказки в зависимости от ситуации. И иногда хочется печатать одну строку, иногда две. Если печать размазана по if/else, код быстро становится похожим на новогоднюю гирлянду: вроде светится, но непонятно, где провод.

Сделаем функцию, которая печатает подсказки:

fun hint(vararg lines: String) {
    for (line in lines) println(line)
}

fun main() {
    val secret = 42
    val guess = 50

    if (guess > secret) {
        hint("Слишком много!", "Попробуйте меньше.")
        // Слишком много!
        // Попробуйте меньше.
    } else if (guess < secret) {
        hint("Слишком мало!")
        // Слишком мало!
    } else {
        hint("Угадали!")
        // Угадали!
    }
}

Здесь вы, по сути, сделали “микро‑API” для сообщений. И это приятная привычка: вы отделяете “что делаем” (логика игры) от “как разговариваем” (сообщения пользователю).

Пример: debug(vararg ...) для отладочной печати

Когда вы учитесь, вы часто вставляете println() просто чтобы понять “что происходит”. Это нормально. Более того: это один из самых честных способов отладки на раннем этапе (пока вы не дружите с debugger).

Но если println() становится слишком много, вы опять получаете шум. Можно сделать маленький “логгер” на минималках: функция принимает тег (строку) и набор сообщений.

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

fun debug(tag: String, vararg messages: String) {
    for (m in messages) {
        println("[$tag] $m")
    }
}

fun main() {
    debug("GAME", "Start", "Secret generated", "Waiting for input")
    // [GAME] Start
    // [GAME] Secret generated
    // [GAME] Waiting for input
}

Так вы получаете удобный вывод, а vararg позволяет передавать сколько угодно сообщений за один вызов.

5. vararg или массив: что выбрать

До vararg у вас уже был способ передать “много значений”: сделать параметр типа массива, например IntArray. Это рабочий подход. Но vararg часто делает вызов проще и приятнее читать.

Вот компактное сравнение:

Подход Как выглядит вызов Что неудобно Когда уместно
fun f(xs: IntArray)
f(intArrayOf(1, 2, 3))
нужно вручную создать массив когда данные уже и так живут в массиве
fun f(vararg xs: Int)
f(1, 2, 3)
нужно продумать случай
f()
когда вы хотите “естественный” вызов перечислением

Про ситуацию “данные уже в массиве, но функция — vararg” мы поговорим отдельно в лекции про spread-оператор, потому что там есть важные синтаксические нюансы. Сегодня мы туда не забегаем: важно просто понять идею vararg и привыкнуть к его вызову.

6. Типичные ошибки при работе с vararg

Ошибка №1: “Я думал, vararg — это волшебная коллекция, а оно как массив”.
На старте легко ожидать, что у vararg появятся какие-то “супер‑методы”. Но на практике проще и правильнее думать так: внутри функции vararg ведёт себя как массив, и базовые операции — это for, size, индексация.

Ошибка №2: забыли, что возможен вызов без аргументов, и случайно упали на xs[0].
Как только вы пишете vararg, вы автоматически разрешаете вызов f(). Если внутри вы сходу делаете xs[0], то при пустом наборе получите ошибку времени выполнения. Даже если “в вашем сценарии так не бывает”, Kotlin всё равно разрешает такой вызов, и вы должны либо обработать его, либо явно запретить.

Ошибка №3: попытались объявить два vararg в одной функции.
Это прям классика: хочется сделать “и список чисел, и список строк”, но Kotlin не даст — vararg может быть только один. Это ограничение языка, и оно специально сделано, чтобы вызовы оставались однозначными.

Ошибка №4: используют vararg “на всякий случай”, хотя аргументов всегда ровно два.
vararg — мощный инструмент, но если по смыслу у функции всегда два значения (например, ширина и высота), то vararg ухудшает читабельность: вызов начинает выглядеть так, будто значений может быть сколько угодно, хотя это не так. На уровне API это вводит в заблуждение, поэтому лучше оставлять обычные параметры, когда количество фиксировано.

Ошибка №5: превращают vararg в способ “спрятать плохой дизайн”.
Иногда встречается подход “а давайте всё передавать vararg’ом, и разберёмся внутри”. Это почти всегда плохо: функция становится непредсказуемой, а ошибки проявляются поздно. vararg хорош, когда набор однородный и логичный (сообщения, числа, строки одного смысла), а не когда это мешок всего подряд.

Ошибка №6: делают слишком много печати через say(vararg lines) и теряют структуру программы.
say(vararg lines: String) — полезная штука, но если начать печатать через неё “всё на свете”, легко потерять понимание, где у вас логика, а где интерфейс. Хороший ориентир: vararg‑функции обычно делают одну маленькую работу (печать, подсчёт, простая обработка набора), а не заменяют собой архитектуру программы.

1
Задача
Kotlin SELF, 16 уровень, 0 лекция
Недоступна
Касса на бегу
Касса на бегу
1
Задача
Kotlin SELF, 16 уровень, 0 лекция
Недоступна
Встреча гостей
Встреча гостей
1
Задача
Kotlin SELF, 16 уровень, 0 лекция
Недоступна
Отчёт по заметкам
Отчёт по заметкам
1
Задача
Kotlin SELF, 16 уровень, 0 лекция
Недоступна
Отладка сервиса
Отладка сервиса
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ