Перейти к основному содержимому

Консольный ввод и вывод в Kotlin

Разработчику

Консольный ввод и вывод

Многие первые программы на Kotlin — консольные — текст в терминале, ввод с клавиатуры, без окон и кнопок. Так же устроены утилиты сборки, скрипты миграции и серверы вроде Ktor — они пишут логи в консоль, даже если основная работа идёт по сети.

Старт с нуля: первая программа. Дальше — корутины, коллекции.


Словарь терминов

ТерминПростыми словами
Консоль / терминалОкно, куда программа печатает текст и откуда читает ввод пользователя.
stdoutСтандартный поток вывода — обычный текст (println).
stderrПоток для сообщений об ошибках — отделён от обычного вывода.
stdinСтандартный ввод — то, что пользователь набирает с клавиатуры.
Аргументы командной строкиСлова после имени программы: java -jar app.jar Анна 25Анна, 25.
Интерполяция строкВставка переменных в строку: "Привет, $name".

Вывод в консоль

fun main() {
print("Введите имя: ") // без перевода строки в конце
println("Готово.") // с переводом строки
System.err.println("Ошибка конфигурации") // в stderr
}

Разбор:

  • Вход: минимальная консольная программа с обычным и error-выводом.
  • Ключевые API: print, println, System.err.println для разных сценариев печати.
  • Поток выполнения: сначала показывается приглашение, затем обычное сообщение, затем ошибка в отдельный поток.
  • Результат: пользователь видит базовый CLI-вывод, а диагностические сообщения отделены от основного текста.
  • Типичная ошибка: печатать ошибки через println, из-за чего сложнее фильтровать логи в CI/скриптах.
ФункцияЭффект
print(...)Печать без \n в конце
println(...)Печать и переход на новую строку
System.err.printlnСообщение об ошибке (в IDE часто красным)

Интерполяция

val n = 42
println("Ответ: $n, квадрат: ${n * n}")

Разбор:

  • $n подставляет значение переменной прямо в шаблон строки.

  • ${n * n} вычисляет выражение внутри фигурных скобок и вставляет результат.

  • формат повышает читаемость по сравнению с ручной конкатенацией строк.

  • $n — подставить значение переменной.

  • ${n * n} — подставить результат выражения (всё внутри фигурных скобок).


Многострочный текст

val help = """
Использование: app <имя>
Пример: app Анна
""".trimIndent()
println(help)

Разбор:

  • тройные кавычки создают raw string с сохранением переносов.
  • trimIndent() убирает общий отступ, чтобы текст выглядел ровно в консоли.
  • println(help) печатает готовую справку одним вызовом.
  • паттерн удобен для --help и многострочных подсказок CLI.

Тройные кавычки сохраняют переносы; trimIndent() убирает общий отступ слева.


Ввод с клавиатуры

С Kotlin 1.6+ для учебных задач удобен readln():

fun main() {
print("Имя: ")
val name = readln()
print("Возраст: ")
val age = readln().toIntOrNull() ?: run {
println("Нужно целое число")
return
}
println("Привет, $name! Через год вам будет ${age + 1}.")
}

Разбор:

  • readln() читает одну строку из stdin до нажатия Enter.
  • toIntOrNull() безопасно парсит число и возвращает null вместо исключения при ошибке.
  • оператор Элвиса ?: запускает run { ... }, если парсинг не удался.
  • return внутри run завершает main, предотвращая работу с невалидным вводом.
  • интерполяция ${age + 1} вычисляет и вставляет выражение в ответ пользователю.

Таблица по ключевым строкам:

СтрокаСмысл
readln()Ждёт Enter, возвращает строку без символа перевода строки.
toIntOrNull()Преобразует "42"42, "abc"null без падения программы.
?: run { ... return }Если число не распарсилось — напечатать сообщение и выйти из main.

readLine() — вариант с nullable

val line = readLine()
if (line.isNullOrBlank()) {
println("Пустой ввод")
}

Разбор:

  • readLine() возвращает nullable-строку String?, поэтому нужна проверка на null.
  • isNullOrBlank() одновременно ловит EOF, null и пустой/пробельный ввод.
  • пример демонстрирует безопасный стиль работы с потенциально отсутствующими данными.

readLine() совместим с Java API и возвращает String? (null при конце ввода / EOF).


Аргументы командной строки

fun main(args: Array<String>) {
if (args.isEmpty()) {
println("Укажите имя: app <имя>")
return
}
val name = args[0]
val greet = args.getOrElse(1) { "Привет" }
println("$greet, $name!")
}

Разбор:

  • args: Array<String> принимает аргументы командной строки при запуске программы.
  • args.isEmpty() проверяет, передал ли пользователь обязательные параметры.
  • args[0] читает первый позиционный аргумент (имя).
  • getOrElse(1) { "Привет" } задаёт значение по умолчанию для второго аргумента.
  • итоговая строка формируется интерполяцией и печатается в stdout.
ВыражениеПример args = ["Анна", "Здравствуй"]
args[0]"Анна"
args.getOrElse(1) { "Привет" }"Здравствуй"; если второго нет — "Привет"

Запуск:

./gradlew run --args="Анна"
java -jar app.jar Анна Здравствуй

Разбор:

  • первая команда запускает приложение через Gradle-задачу run с передачей аргументов.
  • вторая команда запускает уже собранный JAR напрямую через JVM.
  • оба варианта демонстрируют одинаковую модель передачи параметров в main(args).

В IntelliJ IDEA: Run → Edit Configurations → Program arguments.


Цикл "меню"

Код ITЗагрузка примера кода…

Разбор:

  • while (true) запускает бесконечный цикл командного меню.

  • when (readln().trim()) разбирает команду пользователя по строковому коду.

  • ветка "1" читает два числа и печатает сумму.

  • ?: continue пропускает итерацию при невалидном числе без падения программы.

  • ветка "2" завершает цикл через break, else обрабатывает неизвестные команды.

  • when — аналог switch с удобными ветками.

  • continue — пропустить итерацию и снова показать меню, если ввод не число.

  • break — выйти из while.

Без toIntOrNull() строка "два" вызовет NumberFormatException и программа упадёт.


Чтение файла


import java.io.File

fun main() {
val path = readln().trim()
val lines = File(path).readLines()
println("Строк: ${lines.size}")
lines.take(5).forEach { println(it) }
}

Разбор:

  • readln().trim() считывает путь и убирает лишние пробелы по краям.
  • File(path).readLines() загружает файл целиком в память как список строк.
  • lines.size показывает количество прочитанных строк.
  • take(5).forEach { ... } печатает превью первых пяти строк.
  • для крупных файлов стоит перейти на ленивую обработку через useLines.

readLines() загружает весь файл в память. Для гигабайтных логов используйте Sequence и useLines { }.


Кодировка и Windows

Консоль Windows иногда показывает кириллицу некорректно.

  • PowerShell: chcp 65001 перед запуском (UTF-8).
  • Gradle run: JVM-флаг -Dfile.encoding=UTF-8.

В Linux и macOS UTF-8 обычно уже включён.


Частые ошибки

СимптомПричина
NumberFormatExceptiontoInt() вместо toIntOrNull()
Пустая строка после readln()Пользователь нажал Enter без текста — проверяйте isBlank()
Кракозябры в WindowsКодировка консоли — см. выше

Связанные материалы


Дополнительный пример — простая команда sum

Код ITЗагрузка примера кода…

Разбор:

  • Вход: команда ожидает два позиционных аргумента из командной строки.
  • Ключевые API: args, toIntOrNull, System.err.println, println.
  • Поток выполнения: валидируется число аргументов, затем безопасно парсятся оба значения, после чего печатается результат.
  • Результат: утилита предсказуемо считает сумму и корректно сообщает об ошибках ввода.
  • Типичная ошибка: использовать toInt() без валидации; нечисловой аргумент сразу завершит программу исключением.

Запуск:

java -jar app.jar 10 32

Разбор:

  • Вход: после имени JAR передаются два значения.
  • Ключевые API: JVM кладёт эти значения в args функции main.
  • Поток выполнения: программа проходит валидацию и выводит сумму в stdout.
  • Результат: удобный CLI-сценарий, который легко встроить в скрипты.
  • Типичная ошибка: передавать аргументы в кавычках как одну строку ("10 32"), тогда args.size будет равен 1.

Архитектура CLI-приложения

Учебные примеры часто пишут в одном main, но для реальной утилиты удобнее разделить обязанности:

СлойЧто делает
main(args)разбирает аргументы, выбирает команду
CommandHandlerбизнес-логика команды
Outputформатирует и печатает результат
ExitCodeвозвращает код завершения для CI и скриптов

Такой подход упрощает тестирование: логику можно проверить без реального терминала.


Мини-парсер аргументов

data class CliOptions(
val name: String,
val verbose: Boolean
)

fun parseArgs(args: Array<String>): CliOptions? {
val verbose = args.contains("--verbose")
val name = args.firstOrNull { !it.startsWith("--") } ?: return null
return CliOptions(name = name, verbose = verbose)
}

Разбор:

  • data class CliOptions создаёт типизированную модель параметров CLI.
  • args.contains("--verbose") проверяет наличие флага вне зависимости от позиции.
  • firstOrNull { !it.startsWith("--") } ищет первый позиционный аргумент, игнорируя флаги.
  • ?: return null завершает парсинг при отсутствии обязательного имени.
  • возврат CliOptions(...) формирует единый объект конфигурации для остальной логики.
fun main(args: Array<String>) {
val options = parseArgs(args)
if (options == null) {
System.err.println("Использование: app <имя> [--verbose]")
return
}

if (options.verbose) println("Режим verbose включён")
println("Привет, ${options.name}")
}

Разбор:

  • parseArgs(args) выносит разбор параметров в отдельную функцию для тестируемости.
  • if (options == null) обрабатывает ошибку формата аргументов и печатает usage в stderr.
  • options.verbose включает дополнительный диагностический вывод.
  • options.name используется как нормализованный вход после валидации.

Это небольшой шаг к структуре, которую потом легко перенести в сервисный код и покрыть тестами.


Коды завершения и сценарии CI

В shell-скриптах важен код завершения процесса:

  • 0 — успешное выполнение.
  • 1 и выше — ошибка, пайплайн останавливается.

import kotlin.system.exitProcess

fun fail(message: String): Nothing {
System.err.println(message)
exitProcess(1)
}

Разбор:

  • exitProcess(1) завершает процесс с кодом ошибки, который понимают shell и CI.
  • тип возврата Nothing показывает, что функция никогда не возвращает управление.
  • System.err.println(message) отделяет ошибку от обычного вывода.
  • такой helper централизует аварийное завершение и делает код вызовов чище.

Такой шаблон особенно полезен для миграций, импортов данных и утилит сопровождения backend-проекта.