Новости

19.01.2023

Книга «Kotlin. Программирование для профессионалов. 2-е изд.»

Для кого написана эта книга
Мы написали эту книгу для разработчиков разного уровня: тех, кто имеет богатый опыт создания приложений для Android и кому не хватает возможностей Java; тех, кто разрабатывает серверный код и заинтересован в возможностях Kotlin; тех, кто стремится к совместному использованию кода Kotlin в нативных и веб-приложениях; а также для новичков, решившихся на самостоятельное изучение высокопроизводительного компилируемого языка.

Поддержка Android может стать мотивом для изучения Kotlin, но наша книга не ограничивается рассказом о программировании для Android. Более того, весь код в книге не зависит от фреймворка Android. Тем не менее, если вас интересует именно использование Kotlin для разработки Android-приложений, здесь вы найдете основные приемы, которые упростят процесс написания приложений для Android на Kotlin.

Несмотря на то что на Kotlin оказали влияние некоторые другие языки, вам не придется изучать все тонкости этих языков, чтобы успешно работать с Kotlin. Время от времени мы будем приводить код Java, эквивалентный написанному вами коду на Kotlin. Также мы будем указывать на сходство с другими языками там, где это актуально. Программистам с опытом разработки на Java это ­поможет понять связь Kotlin с другими поддерживаемыми платформами. Но если эти ­параллели вам не очень знакомы, примеры решения тех же задач на другом языке полезны, поскольку помогают понять идеи, повлиявшие на формирование Kotlin.

 

Лямбда-выражения и стандартная библиотека Kotlin

Итак, теперь вы знаете основы объявления и вызова функций, использующих лямбда-выражения. Давайте подробнее рассмотрим стандартную библиотеку Kotlin, чтобы понять, как лямбда-выражения применяются в языке. Включите в функцию main вызов функции require, которая была впервые представлена в главе 7.

Листинг 8.16. Возвращение к require (NyetHack.kt)

fun main() {
    narrate("A hero enters the town of Kronstadt. What is their name?") { message ->
        // Выводит сообщение желтым цветом
        "\u001b[33;1m$message\u001b[0m"
    }
    val heroName = readLine() ?: ""
 
    val heroName = readLine()
    require(heroName != null && heroName.isNotEmpty()) {
        "The hero must have a name."
    }
 
    changeNarratorMood()
    narrate("$heroName heads to the town square")
}


Как вы уже догадались, фигурные скобки после вызова require определяют лямбда-выражение. Этому конкретному лямбда-выражению присвоено имя lazyMessage и тип () -> Any.

require проверяет условие, переданное в первом аргументе. Если условие ложно, выдается исключение IllegalArgumentException с сообщением, сгенерированным лямбда-выражением. (Кстати говоря, лямбда-выражение lazyMessage не является обязательным. Если значение не передается, по умолчанию используется пустая строка.) В данном случае лямбда-выражение применяется для оптимизации: строка вычисляется только в том случае, если она необходима для выдачи исключения.

Функциональность lazyMessage не уникальна для require. Собственно, она доступна почти для всех предусловий, о которых мы говорили в главе 7, включая requireNotNull, check, checkNotNull и assert. (Функция error занимает особое место: это единственная функция проверки предусловий, не имеющая ленивого сообщения. Вместо этого она получает сообщение в String.)

В добавление к оптимизации производительности в стандартной библиотеке Kotlin есть ряд функций, позволяющих выражать сложные алгоритмы в одной строке кода.

Допустим, вы хотите присвоить игроку титул, зависящий от его имени, например «Повелитель гласных» (The Master of Vowels), если имя содержит много гласных, или «Узнаваемый» (Identifiable), если имя состоит из цифр. Тип String в Kotlin включает несколько функций с лямбда-аргументами, которые могут пригодиться для принятия подобных решений.

Добавьте в main функцию createTitle. Сначала проверьте, содержит ли имя игрока более четырех гласных, и если содержит, назначьте ему титул The Master of Vowels.

Листинг 8.17. Определение createTitle (NyetHack.kt)

fun main() {
    narrate("A hero enters the town of Kronstadt. What is their name?") { message ->
        // Выводит сообщение желтым цветом
        "\u001b[33;1m$message\u001b[0m"
    }
    val heroName = readLine()
    require(heroName != null && heroName.isNotEmpty()) {
        "The hero must have a name."
    }
    changeNarratorMood()
    narrate("$heroName heads to the town square")
    narrate("$heroName, ${createTitle(heroName)}, heads to the town square")
}

private fun createTitle(name: String): String {
    return when {
        name.count { it.lowercase() in "aeiou" } > 4 -> "The Master of Vowel
        else -> "The Renowned Hero"
    }
}


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

    A hero enters the town of Kronstadt. What is their name?
    Aurelia
    The narrator begins to feel professional.
    Aurelia, The Master of Vowels, heads to the town square.


Другая полезная строковая функция — all — проверяет, что каждый символ строки удовлетворяет заданному предикату (вроде (Char) -> Boolean). Также существует функция none, которая проверяет обратное условие — что ни один символ строки не удовлетворяет заданному предикату.

Определим еще два титула для игрока. Если имя игрока состоит только из цифр, ему должен быть присвоен титул The Identifiable (Узнаваемый), а если оно не содержит ни одной буквы — титул The Witness Protection Member (Участник программы по защите свидетелей). Конечно, цифры тоже не являются буквами, поэтому в выражении when необходимо тщательно упорядочить условия, чтобы все титулы назначались по задуманным вами правилам.

Листинг 8.18. Добавление титулов (NyetHack.kt)

...
private fun createTitle(name: String): String {
    return when {
        name.all { it.isDigit() } -> "The Identifiable"
        name.none { it.isLetter() } -> "The Witness Protection Member"
        name.count { it.lowercase() in "aeiou" } > 4 -> "The Master of Vowels"
        else -> "The Renowned Hero"
    }
}


Протестируйте свой код с входными данными 11, **** и т.д., чтобы убедиться в том, что игрокам присваиваются правильные титулы.

Функции all, none и count позволяют компактно выполнять такие проверки. Без этих функций разработчику пришлось бы самостоятельно реализовать эти классы (вероятно, для этого потребуется 5–10 строк кода на каждое условие).

Стандартная библиотека Kotlin включает множество функций, которые делают ваш код более компактным и понятным. Вы больше узнаете об этих функциях и получите некоторое представление о способах функционального программирования в главе 11.

Для любознательных: ссылки на функции

До сих пор вы объявляли лямбда-выражения для передачи функции в виде аргумента другой функции. Сделать это можно иначе: передать ссылку на функцию. Ссылка на функцию преобразует обычную функцию, определенную с ключевым словом fun, в значение с типом функции. Ссылку на функцию можно использовать везде, где допускается лямбда-выражение.

Допустим, вы хотите выделить из кода лямбда-выражение, выводящее текст желтым цветом. Для этого реализация лямбда-выражения извлекается в отдельную функцию и применяется синтаксис ссылки на функцию (выделен в примере кода ниже):

   fun main() {
        narrate(
            "A hero enters the town of Kronstadt. What is their name?",
            ::makeYellow
        )
        ...
    }

    private fun makeYellow(message: String) = "\u001b[33;1m$message\u001b[0m"
 
    private fun createTitle(name: String): String {
        ...
    }


Чтобы получить ссылку на функцию, примените оператор :: к имени этой функции. Ссылки на функции могут пригодиться во многих ситуациях. Если у вас есть именованная функция, соответствующая требованиям к параметру, который должен получать аргумент-функцию, ссылка на функцию позволит использовать ее вместо определения лямбда-выражения. А может быть, вы захотите передать в аргументе функцию из стандартной библиотеки Kotlin. Ссылки на функции позволяют решать такие задачи еще компактнее, чем лямбда-выражения.

Для любознательных: захват лямбда-выражений

В языке Kotlin лямбда-функция может изменять переменные и ссылаться на них вне своей области видимости. Лямбда-функция захватывает переменные, объявленные за ее пределами, то есть сохраняет ссылки на эти переменные, как было показано на примере первого лямбда-выражения в функции narrate.

Для демонстрации такого свойства лямбда-функций давайте добавим еще одно настроение рассказчика в функцию changeNarratorMood:

Листинг 8.19. Изменение переменных из лямбда-функции (Narrator.kt)

...
fun changeNarratorMood() {
    val mood: String
    val modifier: (String) -> String

    when (Random.nextInt(1..4)) {
    when (Random.nextInt(1..5)) {
        ...
        3 -> {
            mood = "unsure"
            modifier = { message ->
                "$message?"
            }
        }
        4 -> {
            var narrationsGiven = 0
            mood = "like sending an itemized bill"
            modifier = { message ->
                narrationsGiven++
                "$message.\n(I have narrated $narrationsGiven things)"
            }
        }
        else -> {
            mood = "professional"
            modifier = { message ->
                "$message."
            }
        }
    }

    narrationModifier = modifier
    narrate("The narrator begins to feel $mood")
}


Новая переменная настроения учитывает количество вариантов повествования. Прежде чем выполнять код, ненадолго остановитесь и спросите себя: что будет делать этот код? Чтобы подтвердить свои догадки, запустите NyetHack (возможно, программу придется пару раз перезапустить, чтобы добраться до нового настроения рассказчика). Результат будет выглядеть примерно так:

    A hero enters the town of Kronstadt. What is their name?
    Madrigal
    The narrator begins to feel like sending an itemized bill.
    (I have narrated 1 things)
    Madrigal, The Renowned Hero, heads to the town square.
    (I have narrated 2 things)


Ваша догадка подтвердилась? Разберемся, что здесь происходит.

Хотя переменная narrationsGiven определяется за пределами лямбда-функции, последняя может обращаться к переменной и изменять ее. Таким образом, значение narrationsGiven увеличивается с 0 до 1 и с 1 до 2.

Задание: новые титулы и настроения

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

Вот несколько возможных идей для настроения.

• Ленивое: рассказчик выдает только первую часть выводимого сообщения. (Подсказка: воспользуйтесь функциями take или substring типа String.)

• Таинственное: рассказчик использует шифр leet, заменяя буквы похожими цифрами или знаками. Например, L заменяем на 1; E — на 3; T — на 7. (Подсказка: присмотритесь к функции replace типа String. Есть версия функции replace, которая получает во втором параметре лямбда-функцию.)

• Поэтическое: рассказчик делает между словами паузы (вставляем разрывы строк), чтобы придать тексту поэтический стиль. К сожалению, из-за сложности языка и размера у рассказчика вряд ли получится шедевр. (Подсказка: возможных решений много, но как вариант можно объединить функцию replace с классом Random, который мы уже использовали ранее в этой главе.)

А вот несколько идей для титулов, которые можно назначать игрокам.

• «Выдающийся»: назначается игроку, если все буквы в его имени записаны в верхнем регистре.
• «Пространный»: назначается игроку, если в его имени слишком много букв (пороговое значение для «слишком много» выберите сами).
• «Носитель палиндрома»: назначается игроку, если его имя является палиндромом. (Подсказка: обратите внимание на функцию reverse класса String. Не забудьте, что в строках учитывается регистр символов.)


Подробнее с книгой можно ознакомиться в нашем каталоге.


Комментарии: 0

Пока нет комментариев


Оставить комментарий






CAPTCHAОбновить изображение

Наберите текст, изображённый на картинке

Все поля обязательны к заполнению.

Перед публикацией комментарии проходят модерацию.