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 — про виведення, а обчислення краще робити окремо.

5. 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 і чому він працює.

6. Типові помилки під час екранування та 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"), у вас буде зайвий порожній рядок (або зайве перенесення) там, де ви його не очікували. Це не трагедія, але в меню та звітах швидко перетворюється на «чому все зʼїхало».

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ