Магия функций в Kotlin
В этой статье мы рассмотрим самые частые способы употребления «магических» функций в Kotlin, которые упрощают написание и понимание кода.
28К открытий29К показов
В этой статье мы рассмотрим самые распространенные способы «магического» использования функций в Kotlin.
Extension Functions
Начнем с простого: расширения классов без наследования. Мы можем, не изменяя класс String
и все использующие его пакеты, расширить этот класс, добавив новый метод или свойство (поле). Пусть у нас есть метод deleteSpaces()
:
И мы можем использовать этот метод так, как будто он является частью класса String
. Пользователь увидит это так:
После компиляции же этот метод будет выглядеть примерно так (часть кода была упрощена, чтобы вам легче было понять суть):
Отсюда можно сделать вывод, что внутри метода deleteSpaces()
у нас есть доступ только к публичным полям и методам класса, благодаря чему инкапсуляция не нарушается.
Кроме Extension Functions в Kotlin по аналогии могут быть и Extension Properties:
Большинство «магических» применений функций и лямбд в Kotlin, так же как и эта, являются не более чем синтаксическим сахаром, но каким удобным!
Лямбда-функции и анонимные функции
В Kotlin анонимные и лямбда-функции — это функции без имени, но при этом они могут быть использованы как объекты. Их можно писать прямо внутри выражения, минуя отдельное объявление.
Синтаксис лямбда-выражения:
Синтаксис декларации анонимной функции полностью совпадает с синтаксисом обычной функции, но у первой нет имени.
О семантических отличиях между этими видами функций мы поговорим позднее, а сейчас рассмотрим примеры использования лямбд вместе с функциями высшего порядка.
Функции высшего порядка
Функцией высшего порядка называют функцию, принимающую в качестве одного из аргументов другую функцию, в том числе лямбду или анонимную функцию. Яркий пример использования таких функций – callback.
Допустим, у нас есть функция высшего порядка longWork()
:
Она принимает в качестве аргумента функцию callback()
, но вызывает ее только после функции doSomething()
. Использование этой функции:
Здесь мы вызываем функцию longWork()
и передаем ей в качестве аргумента лямбда-функцию, которую она вызовет позже. Koltin позволяет вынести лямбду за скобки, если она — последний аргументом функции, а также вовсе убрать скобки, если лямбда является единственным аргументом. Также в большинстве случаев можно убрать тип возвращаемого значения и заменить аргументы на _
, если они не используются. Вот более короткий вариант:
Но можно и вовсе убрать аргументы у лямбда-функции, если они не нужны:
И это внешне напоминает уже не функцию высшего порядка, а языковую конструкцию, как например synchronized
в Java. К слову, synchronized
в Kotlin построен именно как функция высшего порядка.
Это очень удобно для создания так называемых DSL (Domain-Specific Languages) – предметно-ориентированных языков. Одни из самых популярных DSL для Kotlin — Anko (Android UI прямо в Kotlin с сохранением удобства XML-разметки), Gradle Kotlin DSL (Gradle-скрипты на Kotlin), kotlinx.html (по аналогии с Anko).
Для примера рассмотрим HTML-страницу на Kotlin:
В stdout будет выведено:
Главное преимущество этого DSL в том, что в отличие от декларативного HTML, в Kotlin есть переменные, которые могут быть использованы для генерации динамической страницы. И это намного красивее и удобнее классической генерации страницы через конкатенацию множества строк. В реальности для генерации HTML разметки используют другие методы, этот был показан только как пример DSL в Kotlin.
Другой пример использования функций высшего порядка – как аналог Streams API из Java:
Более сложные лямбды
Рассмотрим пример кода:
С помощью более сложных функций высшего порядка из стандартной библиотеки языка можно превратить код выше в следующее:
Как можно заметить, метод apply()
позволяет не писать несколько раз builder.append()
благодаря следующему прототипу метода:
public inline fun <T> T.apply(block: T.() -> Unit): T
Здесь лямбда-функция block
— метод-расширение для типа T
, в данном случае для StringBuilder
. И append()
внутри лямбды block
— это this.append()
, где this
– экземпляр класса StringBuilder
.
Метод let()
действует схожим образом, только принимает немного другую лямбду:
public inline fun <T, R> T.let(block: (T) -> R): R
Здесь ссылка на объект передается не в качестве this
, а в качестве явного аргумента метода, но мы его не указали. В таких случаях компилятор первый аргумент лямбда-функции автоматически называет «it».
Немного о недосказанном
Во-первых, в Kotlin, в отличие от Java, есть перегружаемые операторы. Так, например, если у класса есть метод plus()
, то его можно вызвать оператором +
, а метод get()
– оператором []
.
Во-вторых, функции в Kotlin могут быть явно помечены как inline
или noinline
. Этот модификатор сообщает компилятору, стоит ли заинлайнить функцию для увеличения производительности или нет. Но здесь кроется подвох: разное поведение return
в inline
и noinline
функциях.
В inline
функциях return
будет произведен из ближайшей по области видимости noinline
функции. В noinline
– из самой функции. Эту проблему решают «именованные return
».
Чтобы сделать return
из лямбды, которую мы передаем в примере выше в apply()
, можно использовать return@apply
.
Именованными могут быть не только return
, но и break
, continue
. Также можно создавать и собственные метки:
Кроме того, существует модификатор функции tailrec
, который говорит компилятору заменить рекурсию в функции на цикл, если она написана в функциональном формате return if-then-else. Пример:
В-третьих, в случае, если метод требует в качестве аргументов объект, который должен быть унаследован от класса/интерфейса с одним абстрактным методом, то в эту функцию можно передать лямбду или анонимную функцию, а компилятор сам создаст анонимный класс с переопределением абстрактного метода на нашу лямбду. Например, в стандартной библиотеке Android есть метод public void setOnClickListener(View.OnClickListener l)
, где OnClickListener
– это интерфейс с единственным методом onClick(View v)
.
Лямбда, переданная в виде setOnClickListener { doSomething() }
, будет скомпилирована в анонимный класс, реализующий интерфейс OnClickListener
, где наша лямбда превратится в метод onClick(View v)
.
Итоги
Это далеко не всё о функциях в Kotlin, только самое часто используемое. Kotlin своими «магическими» функциями позволяет сильно упростить написание и, самое главное, чтение кода. Удобство написания и безопасность – это два самых важных отличия Kotlin от созданной ещё в 1995(!) году Java. В то время об удобстве и безопасности кода только мечтали.
28К открытий29К показов