JavaRush /Курсы /Kotlin SELF /Значения по умолчанию и именованные аргументы

Значения по умолчанию и именованные аргументы

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

1. Значения по умолчанию

Когда вы только начинаете, кажется, что проблема «перепутал параметры местами» — это что-то из мира больших проектов. Но на практике она приходит очень рано: стоит появиться функции с 35 параметрами одного типа (например, несколько Int или несколько Boolean), как мозг начинает нас обманывать. И это не «вы невнимательные», это нормальная человеческая биология: у нас нет встроенного парсера сигнатур.

Решение в Kotlin простое и эффективное: часть параметров можно сделать необязательными через значения по умолчанию, а при вызове можно подписывать аргументы именами. Это превращает вызов функции из волшебного заклинания в понятную фразу, которую можно читать как обычный текст.

Делаем параметр необязательным

Давайте зайдем с простого: очень часто функция вызывается типично, и только иногда хочется уточнить деталь. Например, «напечатай заголовок» почти всегда печатает линию = одной и той же ширины — но иногда хочется шире или другим символом. В таких случаях значение по умолчанию — это как настройка, которую обычно трогать не нужно.

Синтаксис очень простой: в объявлении параметра пишем = …. Kotlin использует это значение, если при вызове не передали аргумент для этого параметра.

fun printHeader(title: String, width: Int = 30, border: Char = '=') {
    val line = border.toString().repeat(width)
    println(line)
    println(title)
    println(line)
}

fun main() {
    printHeader("Кошелёк")                 // печатает ширину 30 и '='
    printHeader("Кошелёк", width = 10)     // печатает ширину 10 и '='
}

Обратите внимание на важный момент: мы не делаем «две версии» функции. Функция одна, но у неё есть «настройки по умолчанию». Это почти всегда лучше для проекта, чем плодить много похожих функций с чуть разными именами.

Дефолт — это не только константа

Может казаться, что значение по умолчанию должно быть чем-то простым вроде 0, true или "text". Но Kotlin разрешает использовать любое выражение, в том числе зависящее от других параметров. Это очень удобно: например, «длина по умолчанию равна размеру массива», или «лимит по умолчанию равен длине строки».

Важно только правило: если дефолт зависит от другого параметра, этот «другой параметр» должен стоять раньше в списке параметров (логично: нельзя ссылаться на то, чего ещё «нет»).

Небольшой пример на нашем проекте «Кошелек»: хотим напечатать список расходов, и по умолчанию печатать всего списка.

fun printExpenses(amounts: IntArray, size: Int, limit: Int = size) {
    var i = 0
    while (i < size && i < limit) {
        println("Расход #${i + 1}: ${amounts[i]}") // например: Расход #1: 150
        i++
    }
}

fun main() {
    val amounts = intArrayOf(150, 40, 999)
    val size = 3

    printExpenses(amounts, size)            // печатает 3 расхода
    printExpenses(amounts, size, limit = 1) // печатает 1 расход
}

Порядок параметров: обязательные вперёд, дефолтные назад

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

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

Сделаем пример «как лучше не делать»:

fun greeting(userId: Int = 0, message: String) {
    println("[$userId] $message")
}

fun main() {
    greeting(message = "Привет!")  // [0] Привет!
    greeting("Привет!")         // так нельзя: компилятор будет ругаться
}

Такой порядок иногда нужен (бывает), но чаще это просто кривой дизайн. В любых задачах и в нашем приложении лучше придерживаться простого правила: «самое важное — сначала, настройки — потом».

Например, вот более приятный вариант:

fun greeting(message: String, userId: Int = 0) {
    println("[$userId] $message")
}

fun main() {
    greeting("Привет!")        // [0] Привет!
    greeting("Привет!", 42)    // [42] Привет!
}

2. Именованные аргументы

Подписываем аргументы и перестаём бояться одинаковых типов

Именованные аргументы нужны не потому, что Kotlin любит многословность, а потому что они резко снижают вероятность ошибки. Особенно когда параметры одинакового типа или когда значение само по себе не объясняет смысл (классика: набор аргументов true, false, true — «что это было?»).

Синтаксис: при вызове пишем имяПараметра = значение. Kotlin позволяет перечислять именованные аргументы в любом порядке — именно ради читабельности.

Сделаем функцию для добавления расхода в массив (пока без категорий, чтобы не забегать вперёд). Пусть у нас есть «дата» как день и месяц — примитивно, но для примера идеально.

fun addExpense(
    amounts: IntArray,
    days: IntArray,
    months: IntArray,
    size: Int,
    amount: Int,
    day: Int,
    month: Int
): Int {
    amounts[size] = amount
    days[size] = day
    months[size] = month
    return size + 1
}

fun main() {
    val amounts = IntArray(10)
    val days = IntArray(10)
    val months = IntArray(10)
    var size = 0

    size = addExpense(amounts, days, months, size, amount = 150, day = 14, month = 1)
    println(size) // 1
}

Если бы мы вызвали это позиционно как addExpense(…), это выглядело бы как «телефонный номер». С именами смысл виден сразу: amount, day, month.

Как менять настройку и не передавать лишнее

Частый сценарий: у функции несколько параметров с дефолтами, и вы хотите поменять не первый из них, а какой-то другой. Kotlin это разрешает, и тут полезно запомнить простое правило: если вы пропускаете дефолтные параметры, то то, что вы задаёте дальше, обычно проще (и безопаснее) задавать именованно.

Документация формулирует это так: вы можете пропустить часть аргументов с дефолтами, но после первого пропущенного аргумента нужно именовать все последующие (иначе непонятно, что к чему относится).

Пример: функция печати отчёта. По умолчанию печатаем и список, и сумму, а валюта — "EUR". Иногда хочется поменять только валюту, не трогая остальные настройки.

fun printReport(
    title: String,
    showList: Boolean = true,
    showTotal: Boolean = true,
    currency: String = "EUR"
) {
    println("== $title ==")
    println("showList=$showList, showTotal=$showTotal, currency=$currency")
}

fun main() {
    printReport("Отчёт") // == Отчёт == / showList=true, showTotal=true, currency=EUR

    printReport("Отчёт", currency = "USD") // == Отчёт == / showList=true, showTotal=true, currency=USD
}

Если бы именованных аргументов не было, нам пришлось бы писать что-то вроде printReport("Отчёт", true, true, "USD") — и снова угадывать, какие там были два true.

Смешиваем позиционные и именованные аргументы

На практике часто получается «гибридный стиль»: первые 12 параметра очень понятны по смыслу (обычно title, text, amount), и их удобно передавать позиционно, а вот дальше начинаются настройки, и их лучше передавать именованно. Kotlin это поддерживает.

Обычно придерживаются простого правила для читаемости: сначала идут позиционные аргументы, а потом — именованные. Такой вызов выглядит аккуратно, как «основная мысль + уточнения».

Давайте применим это к нашему заголовку:

fun printHeader(title: String, width: Int = 30, border: Char = '=') {
    val line = border.toString().repeat(width)
    println(line)
    println(title)
    println(line)
}

fun main() {
    printHeader("Кошелёк", border = '#')      // сначала title, потом настройка
    printHeader("Кошелёк", width = 10, border = '*')
}

Такой стиль особенно полезен, когда вы проектируете «мини-API» для себя: базовые параметры читаются быстро, а редкие настройки не мешают, но всегда доступны.

4. Практика: меню «Кошелёк»

Мини-рефакторинг: делаем меню расширяемым

Теперь соберём небольшой, но цельный кусочек нашего консольного приложения «Кошелёк». Мы не добавляем ничего принципиально нового в логике; мы улучшаем интерфейс функций: делаем параметры по умолчанию и используем именованные аргументы, чтобы main() читался как сценарий.

У нас будет стандартное меню: показать команды, добавить расход, вывести список, вывести отчёт. Мы ещё не делаем «идеальный парсинг» ввода (это будет в следующих уровнях), поэтому пусть будет простое чтение строкой и простые проверки.

fun printMenu(appName: String = "Кошелёк", showHint: Boolean = true) {
    printHeader(appName, width = 25)

    println("1) Добавить расход")
    println("2) Показать расходы")
    println("3) Показать сумму")
    println("0) Выход")

    if (showHint) println("Введите номер команды и нажмите Enter") // Введите номер команды...
}

fun printHeader(title: String, width: Int = 30, border: Char = '=') {
    val line = border.toString().repeat(width)
    println(line)
    println(title)
    println(line)
}

fun main() {
    printMenu()                       // дефолтное имя и подсказка
    printMenu(showHint = false)       // тот же экран, но без подсказки
    printMenu(appName = "Budget v0")  // кастомное имя
}

Здесь важна сама идея: мы сделали поведение по умолчанию «типичным» для большинства вызовов, а редкие настройки вынесли в параметры. И из-за именованных аргументов printMenu(showHint = false) выглядит как читаемая фраза.

Памятка: дефолты и именование

В этот момент обычно хочется спросить: «Окей, а как понять, что лучше: дефолт или именование?» Хорошая новость: тут есть практические критерии. Дефолты — это способ сделать «обычный путь» короче, а именованные аргументы — способ сделать «необычный путь» безопаснее и понятнее.

Ситуация Что лучше использовать Почему
Параметр почти всегда одинаковый Значение по умолчанию Убираем шум из большинства вызовов
Несколько параметров одного типа (Int, Boolean, Char) Именованные аргументы Снижаем шанс перепутать порядок
Нужно поменять «настройку в конце», не трогая остальные Дефолты + именование нужной настройки Не приходится передавать «лишние» аргументы
Вызов плохо читается без расшифровки Именованные аргументы Вызов становится самодокументируемым

И да, иногда правильный ответ — «функция перегружена параметрами и её надо упростить». Но это уже ощущение, которое придёт чуть позже, когда функций станет много.

Схема решения: делать ли параметр дефолтным

Чтобы закрепить подход, вот простая блок-схема, по которой удобно принимать решение в своих проектах:

flowchart TD
    A[Есть функция с параметром P] --> B{P обязателен всегда?}
    B -->|Да| C[Оставляем без дефолта]
    B -->|Нет| D{Есть безопасное ожидаемое значение?}
    D -->|Да| E[Делаем дефолт: P: Type = ...]
    D -->|Нет| F[Оставляем обязательным, но вызываем именованно]
    E --> G{Параметры одного типа и легко перепутать?}
    F --> G
    G -->|Да| H[Рекомендуем именованные аргументы]
    G -->|Нет| I[Можно позиционно, если читаемо]

Слово «безопасное» здесь ключевое. Если дефолт может привести к странному поведению (например, дефолтный month = 0 — а у нас месяцы с 1), то лучше заставить явно передавать значение.

5. Типичные ошибки

Ошибка №1: дефолт «на авось», который выглядит удобно, но ломает смысл.
Иногда хочется поставить дефолт, лишь бы параметр стал необязательным. Например, сделать day: Int = 0. Но если 0 — невалидный день, вы создаёте мину замедленного действия: программа будет работать, но данные станут мусорными. Хороший дефолт — это не «любой», а ожидаемый и безопасный.

Ошибка №2: обязательные параметры стоят после дефолтных, и вызовы превращаются в загадки.
Такой дизайн вынуждает постоянно использовать именованные аргументы даже там, где не хотелось бы. Kotlin прямо показывает эту ситуацию: если дефолтный параметр стоит перед обязательным, то опустить его можно только именованным вызовом. В учебных проектах почти всегда проще переставить параметры местами.

Ошибка №3: несколько одинаковых по типу аргументов передаются позиционно, и порядок путается.
Три числа подряд или три булевых флага подряд выглядят одинаково, и ошибка в одном месте очень трудно заметна глазами. В таких вызовах именованные аргументы — это не «болтовня», а страховка.

Ошибка №4: хаотичное смешивание позиционных и именованных аргументов.
Когда часть аргументов передаётся позиционно, часть именованно, а часть снова позиционно (или просто в странном порядке), чтение превращается в боль. Kotlin разрешает именованные аргументы в любом порядке, но цель не в том, чтобы удивить коллегу, а в том, чтобы сделать код очевидным: сначала основное, потом настройки.

Ошибка №5: слишком много параметров с дефолтами как попытка заменить нормальный дизайн.
Если у функции 8 параметров, из них 6 с дефолтами, и вы сами не помните, что означает каждый — это сигнал, что функция делает слишком много. В рамках этого курса мы пока не будем уходить в продвинутые конструкции, но как минимум стоит остановиться и спросить себя: «А нельзя ли разделить действие на две более простые функции?»

1
Задача
Kotlin SELF, 8 уровень, 3 лекция
Недоступна
Баннер приложения
Баннер приложения
1
Задача
Kotlin SELF, 8 уровень, 3 лекция
Недоступна
Сумма на отрезке
Сумма на отрезке
1
Задача
Kotlin SELF, 8 уровень, 3 лекция
Недоступна
Текстовый генератор
Текстовый генератор
1
Задача
Kotlin SELF, 8 уровень, 3 лекция
Недоступна
Два способа
Два способа
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Yan Уровень 1
7 апреля 2026
Задача 2: - Исправить синтаксис диапазона: в Kotlin нет оператора '..<'; Это как так?