JavaRush /Курсы /Kotlin SELF /Экранирование и string templates: символы \, " и $

Экранирование и string templates: символы \, " и $

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

1. Введение

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

Представьте, что вы пишете письмо, но каждое слово STOP внезапно означает «закончить письмо и отправить». В какой-то момент вы захотите написать слово STOP как слово, а не как команду. Экранирование — это ваш «режим цитаты»: оно превращает «команду» обратно в «букву на бумаге».

Обычная строка "..." и особенные символы

В Kotlin обычная строка пишется в двойных кавычках:

val s = "Привет!"

С этого момента компилятор начинает внимательно следить за двумя вещами.

Во‑первых, двойная кавычка " внутри строки закрывает строку. То есть если вы просто так напишете:

val s = "Он сказал: "Привет""

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

Во‑вторых, обратный слеш \ внутри строки — это не «просто палочка», а сигнал: «Сейчас будет escape‑последовательность». То есть \n — это не два символа \ и n, а один специальный символ «перевод строки».

Эти два правила (кавычка закрывает строку, слеш запускает escape) — главные источники «почему оно не компилируется» в строках. И именно их мы сегодня приручим.

2. Escape‑последовательности: \n и \t

Когда вы пишете \n внутри обычной строки, Kotlin создаёт один символ, который означает перевод строки (Enter). То есть вывод пойдёт на новую строку.

Когда вы пишете \t, это символ табуляции — «таб», который обычно визуально выглядит как несколько пробелов, но на самом деле это отдельный управляющий символ. Он полезен, когда вы хотите простенько выровнять колонки текста без сложного форматирования.

Давайте посмотрим на это на мини‑примере. Мы будем развивать еще одно учебное консольное приложение: NoteKeeper — маленький блокнот заметок. Пока он не «умный», зато честный.

fun main() {
    println("NoteKeeper\n1) Добавить заметку\n2) Показать все\n0) Выход")
}

Здесь \n делает текст многострочным. Вывод будет примерно такой:

NoteKeeper
1) Добавить заметку
2) Показать все
0) Выход

Теперь пример с табуляцией:

fun main() {
    println("Команда\tОписание")
    println("add\tДобавить заметку")
    println("list\tПоказать все")
}

Вывод этой программы я написал рядом в комментариях:

fun main() {
    println("Команда\tОписание")      // Команда    Описание
    println("add\tДобавить заметку")  // add        Добавить заметку
    println("list\tПоказать все")     // list       Показать все
}

Пока не пытайтесь сделать из \t «идеальную таблицу уровня Excel». Это простой инструмент. Если строки будут разной длины, табы не всегда дадут идеально ровные колонки — но для учебных меню и подсказок это вполне годится.

3. Экранирование кавычек и обратного слеша

Теперь возьмём два самых популярных «сломателя строк».

Как вывести двойную кавычку " внутри строки

Чтобы кавычка не закрыла строку, её нужно экранировать: \".

fun main() {
    val quote = "Он сказал: \"Привет\""
    println(quote) // Он сказал: "Привет"
}

То есть \" внутри строки означает «вставь в текст обычную кавычку».

Это особенно полезно, когда вы хотите красиво показать пользователю ввод или данные в отчёте. Например: Заметка "Купить молоко" добавлена.

Как вывести обратный слеш \ внутри строки

Чтобы получить один символ \ внутри обычной строки, нужно написать два: \\.

fun main() {
    val path = "C:\\Users\\alex\\notes.txt"
    println(path) // C:\Users\alex\notes.txt
}

Почему так? Потому что первый \ говорит «я начинаю escape‑последовательность», а второй уточняет «а именно — мне нужен символ обратного слеша».

И вот здесь живёт классическая ловушка:

fun main() {
    val bad = "C:\new\test"
    println(bad)
}

На вид это «путь». А по факту Kotlin увидит \n (перевод строки) и \t (табуляцию). То есть строка превратится в неожиданную кашу, и вы будете очень долго смотреть на вывод с мыслью «я же просто хотел сохранить файл». Виноват не Kotlin — виноват обратный слеш как спецсимвол.

4. String templates и символ $

String templates в Kotlin — это способ вставлять значения прямо в строку без конкатенации +: $a для имени и ${...} для выражения.

Подстановка переменных и выражений

Самый простой вариант — подставить переменную:

fun main() {
    val title = "Планы на день"
    println("Заметка: $title") // Заметка: Планы на день
}

Если нужно вставить выражение, а не просто имя, используются фигурные скобки:

fun main() {
    val a = 7
    val b = 5
    println("$a + $b = ${a + b}") // 7 + 5 = 12
}

Это читается так: «вставь a, вставь b, а потом вычисли a + b и вставь результат». Практическое правило простое: имя — без скобок, выражение — в ${...}.

Ловушка: почему $a + b — не то же самое, что ${a + b}

Очень типичная ошибка новичка: вы хотите вывести сумму, пишете «по‑человечески», и получаете не то.

fun main() {
    val a = 2
    val b = 3
    println("$a + $b = $a + $b") // 2 + 3 = 2 + 3
}

С точки зрения Kotlin это абсолютно логично: $a — это подстановка переменной a, дальше идёт текст + , дальше подстановка $b, дальше текст = , дальше снова $a, дальше текст + , дальше $b. Никакой суммы тут не было.

А теперь правильный вариант:

fun main() {
    val a = 2
    val b = 3
    println("$a + $b = ${a + b}") // 2 + 3 = 5
}

Фигурные скобки включают «режим выражения». И это правило стоит запомнить как рефлекс: как только внутри подстановки появляется хотя бы один оператор (+, -, *, /), вызов функции или обращение к свойству — всегда нужны ${...}.

Как вывести $ как обычный текст

$ в Kotlin — это начало string template. Поэтому попытка написать:

val s = "Цена: $"

закончится ошибкой компиляции: Kotlin ожидает после $ имя ($price) или { (${price}), а вы просто завершили строку.

Правильный способ вывести знак $ как текст — экранировать его: "\$".

fun main() {
    val price = 19
    println("Цена: \$${price}") // Цена: $19
}

Разберём по символам один раз, чтобы стало понятно.

В начале \$ печатает буквальный доллар. Затем ${...} подставляет число. В итоге получается $19.

В NoteKeeper это пригодится, например, если мы захотим хранить заметки‑расходы (пока просто как текст). Допустим, заметка: Кофе $4. Тогда вывод может выглядеть так:

fun main() {
    val item = "Кофе"
    val cost = 4
    println("Покупка: $item, цена: \$${cost}") // Покупка: Кофе, цена: $4
}

Иногда «супер‑умная строка» хуже, чем два val

На этом месте часто появляется соблазн: «О, шаблоны умеют в выражения! Давайте запихнём туда всё!» И получается строка, которая выглядит как заклинание на древнем языке.

Технически Kotlin позволяет писать довольно сложные выражения в ${...}. Но хороший стиль — сначала посчитать, потом вставить. Это делает код читаемее, и вам (и вашим будущим коллегам, и вашему будущему “я через неделю”) проще отлаживать.

Сравните два варианта. Слишком умный вариант:

fun main() {
    val a = 10
    val b = 3
    println("Результат: ${a / b}, остаток: ${a % b}") // Результат: 3, остаток: 1
}

Это ещё терпимо, но уже на грани. Второй вариант сильно лучше:

fun main() {
    val a = 10
    val b = 3
    val div = a / b
    val rem = a % b

    println("Результат: $div, остаток: $rem") // Результат: 3, остаток: 1
}

Здесь у вас имена обретают смысл: div и rem. И это гораздо легче читать.

Kotlin поощряет string templates вместо конкатенации. Но «использовать templates» не равно «прятать всю логику внутрь templates». Templates — про вывод, а вычисления лучше делать отдельно.

6. NoteKeeper: сообщения и меню

Сейчас мы сделаем небольшой (но очень жизненный) апгрейд нашего консольного NoteKeeper: научимся печатать понятные сообщения, в которых есть кавычки, переносы строк и шаблоны.

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

fun main() {
    val notes = Array(5) { "" }
    var count = 0

    val newNote = "Купить молоко"
    notes[count] = newNote
    count++

    println("Добавлено: \"$newNote\" (всего заметок: $count)")
    // Добавлено: "Купить молоко" (всего заметок: 1)
}

Тут мы показали заметку в кавычках через \", чтобы пользователь видел границы текста. Мы использовали $newNote и $count, чтобы не склеивать строки через +. И мы получили читаемую строку, в которой легко увидеть, что именно печатается.

Теперь сделаем простое меню как одну строку с \n, чтобы печатать его одной командой:

fun main() {
    val menu =
        "NoteKeeper\n" +
        "1) Добавить заметку\n" +
        "2) Показать все\n" +
        "0) Выход"

    println(menu)
}

Да, тут есть +. Мы пока используем его сознательно: многострочные raw strings """...""" будут в следующей лекции, и там вы увидите способ гораздо красивее. Пока же ваша цель — понять, что такое \n и почему он работает.

7. Типичные ошибки при экранировании и string templates

Ошибка №1: забывают, что \ в обычной строке — служебный символ.
Чаще всего это всплывает на Windows‑путях. Строка "C:\new\test" неожиданно превращается в «C:», затем перенос строки из‑за \n, затем табуляция из‑за \t, а потом ещё какие-то странные символы. Если вам нужен буквальный обратный слеш, пишите \\.

Ошибка №2: пытаются вставить выражение без ${...}.
"$a + $b = $a + $b" выглядит правдоподобно, но это просто текст. Для вычисления суммы нужен именно шаблон выражения: "${a + b}". Это правило следует из самой модели string templates в Kotlin: имя — $a, выражение — ${...}.

Ошибка №3: пытаются вывести $ как обычный символ и получают ошибку компиляции.
Строка "Цена: $" не «печатает доллар», а ломается, потому что $ — это начало шаблона. Чтобы напечатать знак доллара, используйте "\$", а если рядом нужно число — комбинируйте \$ и ${price} как в "\$${price}".

Ошибка №4: перегружают одну строку вычислениями, и потом не могут это отлаживать.
Когда внутри "..." становится слишком много ${...}, особенно с операторами и сложными выражениями, строка превращается в ребус. Гораздо спокойнее сначала вычислить в отдельные val, а затем вывести через простые $name.

Ошибка №5: путают эффект println и \n.
println() и так добавляет перевод строки в конце. Если вы пишете println("Привет\n"), у вас будет лишняя пустая строка (или лишний перенос) там, где вы его не ожидали. Это не трагедия, но в меню и отчётах быстро приводит к «почему всё съехало».

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