Консольный ввод и вывод в 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 обычно уже включён.
Частые ошибки
| Симптом | Причина |
|---|---|
NumberFormatException | toInt() вместо toIntOrNull() |
Пустая строка после readln() | Пользователь нажал Enter без текста — проверяйте isBlank() |
| Кракозябры в Windows | Кодировка консоли — см. выше |
Связанные материалы
- Первая программа
- Коллекции и Sequence
- Простые приложения
- Ktor — сервер с логами в консоль
Дополнительный пример — простая команда 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-проекта.