Files
obsidian/Kotlin/Синтаксис и типы/Базовый синтаксис/Строковые шаблоны и вывод.md
T

8.8 KiB
Raw Blame History

title, description, tags
title description tags
Строковые шаблоны и вывод Интерполяция строк, raw strings, trimIndent, println и форматированный вывод
kotlin
syntax

Шаблоны строк

Вместо конкатенации - подстановка значений прямо в строку:

val name = "Kotlin"
val version = 2.4

println("Привет, $name!")              // Привет, Kotlin!
println("Версия: $version")            // Версия: 2.4
println("Длина имени: ${name.length}") // Длина имени: 6
println("Сумма: ${2 + 3}")             // Сумма: 5

Правила:

  • $имя - для простой переменной
  • ${выражение} - для всего остального: вызовов, свойств, арифметики
  • вызывается toString() подставляемого значения
  • null подставляется как текст null
val x: String? = null
println("значение: $x")    // значение: null

Фигурные скобки можно ставить всегда - многие так и делают для единообразия.

Экранирование

println("цена: \$100")      // цена: $100
println("перенос\nстроки")
println("табуляция\tтут")
println("кавычка \" внутри")
println("слеш \\ внутри")
println("символ \u00A9")    // ©

Поддерживаются \t, \b, \n, \r, \', \", \\, \$, \uXXXX.

Raw strings

Три кавычки - никакого экранирования, переносы сохраняются как есть:

val path = """C:\Users\name\file.txt"""   // слеши не экранируются
val json = """
    {
        "name": "Kotlin",
        "stable": true
    }
"""

Удобно для regex, JSON, SQL, HTML.

Шаблоны $ в raw strings работают:

val user = "admin"
val query = """SELECT * FROM users WHERE name = '$user'"""

Чтобы вставить настоящий $, используют подстановку символа:

val price = """цена: ${'$'}100"""   // цена: $100

trimIndent и trimMargin

Raw string сохраняет отступы исходного кода - их убирают явно:

val text = """
    Первая строка
    Вторая строка
""".trimIndent()
// "Первая строка\nВторая строка" — общий отступ убран,
// пустые первая и последняя строки удалены

val bar = """
    |Первая
    |Вторая
""".trimMargin()           // по умолчанию маркер |

trimIndent() вычисляет минимальный общий отступ непустых строк и срезает его у всех.

println и print

print("без переноса")
println("с переносом")
println()                 // пустая строка
println(listOf(1, 2, 3))  // [1, 2, 3]

Это функции стандартной библиотеки, доступны без импорта. Под капотом - System.out.

Форматированный вывод

Kotlin использует механизм форматирования Java:

import java.util.Locale

fun main() {
    println("%05d".format(42))                   // 00042
    println("%-10s|".format("left"))             // left      |
    println(String.format("%s = %d", "x", 5))    // x = 5

    // вещественные числа и разряды зависят от локали — её лучше задавать явно
    println("%.2f".format(Locale.US, 3.14159))   // 3.14
    println("%,d".format(Locale.US, 1234567))    // 1,234,567
}

[!warning] Локаль по умолчанию format без явной локали берёт системную. На русской локали получится 3,14 и 1 234 567 - с запятой и неразрывными пробелами. Если строка идёт в JSON, SQL или лог, это ломает парсинг. Для машинных форматов всегда передавайте Locale.US (или Locale.ROOT).

Основные спецификаторы:

Спецификатор Значение
%d целое число
%f вещественное
%.Nf вещественное с N знаками
%s строка (через toString())
%b boolean
%x шестнадцатеричное
%e экспоненциальная запись
%n перенос строки платформы
%% символ процента

Флаги ширины: %10d - по правому краю, %-10d - по левому, %010d - с ведущими нулями.

Чтение ввода

val line = readLine()              // String? — null при конце потока
val text = readln()                // String — бросает исключение при EOF
val n = readln().toInt()           // падает на некорректном вводе
val safe = readln().toIntOrNull() ?: 0   // безопасный разбор

readln() появился в Kotlin 1.6 и удобнее, когда ввод гарантирован.

Конкатенация

Работает, но в шаблонах почти не нужна:

println("Hello" + " " + "world")   // Hello world
println("Hello" + 5 + 10)          // Hello510 — слева направо
println("Hello" + (5 + 10))        // Hello15

Важно: складывать можно только когда слева строка. У Int нет plus(String), поэтому в Kotlin не скомпилируется то, что в Java работает:

// println(5 + 10 + "Hello")    // ошибка: Int.plus ожидает Int, а не String
println("${5 + 10}Hello")       // 15Hello — через шаблон

Для сборки строки в цикле - StringBuilder или buildString:

val result = buildString {
    repeat(3) { append("ха") }
}
println(result)      // хахаха

Подробнее: Строки

[!info] Отличия от Java

Java Kotlin
System.out.println(x) println(x)
"a" + x + "b" "a${x}b"
String.format("%d", n) "%d".format(n)
text blocks """ (Java 15+) raw strings """ + шаблоны
.stripIndent() .trimIndent()
new Scanner(System.in) readln()

Шаблоны - не просто синтаксический сахар: компилятор собирает их через StringBuilder или invokedynamic, то есть без промежуточных строк. Писать "a" + b + "c" ради производительности не нужно.

Сравнить: Форматированный вывод в Java

[!warning] Подводные камни $ перед словом всегда считается шаблоном. Строка "цена: $100" скомпилируется (цифра не может начинать имя), а "$name_suffix" - нет: Kotlin ищет переменную name_suffix. Нужны скобки: "${name}_suffix".

trimIndent() чувствителен к смешению табов и пробелов - отступ считается по символам, таб и четыре пробела для него разные вещи.

$ в regex внутри raw string. """^\d+$""" работает, потому что после $ идёт конец строки. Но """$\d""" компилятор попытается разобрать как шаблон.

Шаблон вызывает toString() у массива, а не его содержимое:

val arr = arrayOf(1, 2, 3)
println("$arr")                      // [Ljava.lang.Object;@1b6d3586
println(arr.contentToString())        // [1, 2, 3]
println("${arr.joinToString()}")      // 1, 2, 3

readLine() возвращает String?. Вызов readLine().toInt() не скомпилируется - нужен readln() или обработка null.

Дополнительно: Строки Далее: Пакеты и импорты