Все вопросы:
В этой статье:
(нажмите для перехода)
➤ Что такое внутренние классы (Inner Classes) в Kotlin
➤ Какие есть функции области видимости (Scope Functions) в Kotlin
➤ Как работает корутина и что такое suspend
➤ Какая разница между методом запуска корутин launch и async
➤ Что такое Делегаты в Kotlin
➤ Что такое Extension в Kotlin
➤ Что такое companion object в Kotlin
➤ Как использовать геттеры (get) и сеттеры (set) в Kotlin
➤ Что такое аннотация @JvmStatic
➤ Что такое обратный вызов (callback), функциональные типы и Unit в Kotlin и как используется
➤ Что такое внутренние классы (Inner Classes) в Kotlin
В Kotlin внутренние классы (inner classes) — это классы, которые объявлены внутри другого класса. Они имеют доступ к членам внешнего класса и могут использоваться для реализации паттернов проектирования, таких как “Стратегия” или “Наблюдатель”.
В отличие от Java, в Kotlin внутренние классы по умолчанию являются статическими. То есть, они не имеют доступа к нестатическим членам внешнего класса и могут быть созданы без создания экземпляра внешнего класса.
Однако, если внутренний класс помечен ключевым словом inner, то он становится нестатическим, и тогда он получает доступ к нестатическим членам внешнего класса и требует экземпляра внешнего класса для создания.
Пример внутреннего класса в Kotlin:
class Outer { private val outerField = "Outer field" inner class Inner { fun innerMethod() { println("Accessing outer field: $outerField") } } } fun main() { val outer = Outer() val inner = outer.Inner() inner.innerMethod() // Выведет "Accessing outer field: Outer field" }
➤ Какие есть функции области видимости (Scope Functions) в Kotlin
В Kotlin есть пять функций области видимости (Scope Functions), которые позволяют изменять область видимости переменных, а также упрощают чтение кода и уменьшают вероятность ошибок:
let:
позволяет выполнить блок кода на объекте, переданном в качестве аргумента, и вернуть результат этого блока. Внутри блока можно использовать ссылку на объект через it.
val result = someObject?.let { it.property } ?: defaultValue
run:
выполняет блок кода на объекте, переданном в качестве this, и возвращает результат этого блока. Внутри блока можно использовать ссылку на объект через this.
val result = someObject?.run { property } ?: defaultValue
with:
выполняет блок кода, который передает объект в качестве аргумента, и возвращает результат этого блока.
val result = with(someObject) { property } ?: defaultValue
apply:
выполняет блок кода на объекте, переданном в качестве this, и возвращает этот объект. Внутри блока можно использовать ссылку на объект через this.
val someObject = SomeClass().apply { property1 = value1 property2 = value2 }
also:
позволяет выполнить блок кода на объекте, переданном в качестве аргумента, и вернуть этот объект. Внутри блока можно использовать ссылку на объект через it.
val someObject = SomeClass().also { it.property1 = value1 it.property2 = value2 }
их можно комбинировать
variable?.let { переменная не null } ?: run { переменная null } // то же самое, что if(variable != null) { переменная не null } else { переменная null }
➤ Как работает корутина и что такое suspend
Обычная функция:
не имеют состояния и всегда запускаются «с чистого листа» (если, конечно, не используют глобальные переменные)
должна завершить свое исполнение, перед тем как вернуть управление вызвавшему ее коду
Корутина:
имеет состояние и может приостанавливать и возобновлять свое исполнение в определенных точках, возвращая, таким образом, управление еще до завершения своего исполнения.
Корутина не привязана к нативному потоку, она может быть приостановить выполнение в одном потоке, а возобновить выполнение в другом, в корутинах нет собственного стека, не требует переключения контекста процессора, поэтому работает быстрей.
Есть две функции для запуска корутины:
launch{} и async{}.
launch{}:
ничего не возвращает
async{}:
возвращает экземпляр Deferred, в котором имеется функция await(), которая возвращает результат корутины
CoroutineScope:
отслеживает любую корутину, созданную с помощью launchили async (это функции расширения в CoroutineScope).
Текущая работа (запущенные корутины) может быть отменена вызовом scope.cancel() в любой момент времени.
Job:
управляющий корутиной элемент. Для каждой создаваемой корутины (с помощью launch или async) он возвращает экземпляр Job, который однозначно идентифицирует корутину и управляет ее жизненным циклом. Job может проходить через множество состояний: новое, активное, завершение, завершенное, отмена и отмененное. Хотя у нас нет доступа к самим состояниям, мы можем получить доступ к свойствам Job: isActive, isCancelled и isCompleted.
CoroutineContext:
это набор элементов, определяющих поведение корутины.
Он состоит из:
Job: управляет жизненным циклом корутины.
CoroutineDispatcher: отправляет работу в соответствующий поток.
CoroutineName: имя корутины, полезно для отладки.
CoroutineExceptionHandler: обрабатывает неотловленные исключения, которые будут рассмотрены в 3 части серии о корутинах.
В корутинах есть delay(1000L)- не блокирующая задержка
suspend fun doSomeWork() = coroutineScope { launch { doWork() } } suspend fun doWork() { println("before") delay(400L) println("after") }
Поскольку в этой функции применяется функция delay(), то doWork() определена с модификатором suspend. Сама корутина создается также с помощью функции launch(), которая вызывает функцию doWork().
При вызове задержки с помощью функции delay(), эта корутина освобождает поток, в котором она выполнялась, и сохраняется в памяти. А освобожденный поток может быть задействован для других задач. А когда завершается запущенная задача (например, выполнение функции delay()), корутина возобновляет свою работу в одном из свободных потоков.
➤ Какая разница между методом запуска корутин launch и async
Есть две функции для запуска корутины: launch{} и async{}.
launch{}:
ничего не возвращает,
fun main(args: Array<String>) { print("1 ") val job: Job = GlobalScope.launch { print("3 ") delay(1000L) print("4 ") } print("2 ") // 1 2 но дальше не выполнится так как программа закончится раньше // либо можно сделать job.cancel() }
async{}:
возвращает экземпляр Deferred, в котором имеется функция await(), которая возвращает результат корутины, прямо как Future в Java, где мы делаем future.get() для получения результата.
suspend fun main(args: Array<String>) { print("1 ") val deferred: Deferred<String> = GlobalScope.async { return@async "3 " } print("2 ") print(deferred.await()) print("4 ") // 1 2 3 4 }
➤ Что такое Делегаты в Kotlin
Делегирование классов:
Ключевое слово by в оглавлении Derived, находящееся после типа делегируемого класса, говорит о том, что объект b типа Base будет храниться внутри экземпляра Derived, и компилятор сгенерирует у Derived соответствующие методы из Base, которые при вызове будут переданы объекту b
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // prints 10 }
Делегирование свойств:
Существует несколько основных видов свойств, которые мы реализовываем каждый раз вручную в случае их надобности. Однако намного удобнее было бы реализовать их раз и навсегда и положить в какую-нибудь библиотеку. Примеры таких свойств:
ленивые свойства (lazy properties): значение вычисляется один раз, при первом обращении
свойства, на события об изменении которых можно подписаться (observable properties)
свойства, хранимые в ассоциативном списке, а не в отдельных полях
Для таких случаев, Kotlin поддерживает делегированные свойства.
Выражение после by — делегат: обращения (get(), set()) к свойству будут обрабатываться этим выражением. Делегат не обязан реализовывать какой-то интерфейс, достаточно, чтобы у него были методы getValue() и setValue() с определённой сигнатурой
class Example { var p: String by Delegate() } class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, спасибо за делегирование мне '${property.name}'!" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$value было присвоено значению '${property.name} в $thisRef.'") } }
Стандартные делегаты:
Ленивые свойства (lazy properties):
первый вызов get() запускает лямбда-выражение, переданное lazy() в качестве аргумента, и запоминает полученное значение, а последующие вызовы просто возвращают вычисленное значение.
val lazyValue: String by lazy { println("computed!") "Hello" } val lazyValue: String by lazy { println("computed!") "Hello" } fun main(args: Array<String>) { println(lazyValue) // computed! Hello println(lazyValue) // Hello }
Observable свойства:
Функция Delegates.observable() принимает два аргумента: начальное значение свойства и обработчик (лямбда), который вызывается при изменении свойства. У обработчика три параметра: описание свойства, которое изменяется, старое значение и новое значение.
class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" user.name = "second" } // вывод // <no name> -> first // first -> second
Хранение свойств в ассоциативном списке:
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } val user = User(mapOf( "name" to "John Doe", "age" to 25 ))
➤ Что такое Extension в Kotlin
Kotlin позволяет расширять класс путём добавления нового функционала.
Расширения на самом деле не проводят никаких модификаций с классами, которые они расширяют. Объявляя расширение, вы создаёте новую функцию, а не новый член класса. Такие функции могут быть вызваны через точку, применимо к конкретному типу.
Расширения имеют статическую диспетчеризацию: это значит, что вызванная функция-расширение определяется типом её выражения во время компиляции, а не типом выражения, вычисленным в ходе выполнения программы, как при вызове виртуальных функций.
fun Any?.toString(): String { if (this == null) return "null" return toString() }
➤ Что такое companion object в Kotlin
Что то вроде замены статики в Java, к ним обращаются через название класса. Такие члены вспомогательных объектов выглядят, как статические члены в других языках программирования. На самом же деле, они являются членами реальных объектов и могут реализовывать, к примеру, интерфейсы:
interface Factory<T> { fun create(): T } class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() } }
➤ Как использовать геттеры (get) и сеттеры (set) в Kotlin
var stringRepresentation: String get() = this.toString() set(value) { setDataFromString(value) } var setterVisibility: String = "abc" private set // сеттер имеет private доступ и стандартную реализацию
➤ Что такое аннотация @JvmStatic
Указывает, что из этого элемента должен быть сгенерирован дополнительный статический метод,если это функция.Если этот элемент является свойством,то должны быть сгенерированы дополнительные статические методы получения/установки.
➤ Что такое обратный вызов (callback), функциональные типы и Unit в Kotlin и как используется
Unit то же что и void в Java, не возвращает ничего.
fun main(args: Array<String>) { one { variable -> print(variable) } two { variable -> print(variable) return@two "4 " } three { return@three "5 " } four({ variableOne -> print(variableOne) }, { variableTwo -> print(variableTwo) }) val listenerOne: (() -> Unit) = { print("8 ") } listenerOne.invoke() val listenerTwo: ((String) -> Unit) = { variable -> print(variable) } listenerTwo.invoke("9 ") val listenerThree: ((String) -> String) = { variable -> variable } val result = listenerThree.invoke("10 ") print(result) } fun one(callback: (String) -> Unit) { // передаем в лямбду и передаем в нее String, ничего не ожидаем обратно (Unit) callback("1 ") } fun two(callback: (String) -> String) { // передаем в лямбду и передаем в нее String, ожидаем обратно String callback("2 ") print(callback("3 ")) } fun three(callback: () -> String) { // вызываем лямбду но ничего в нее не передаем, ожидаем обратно String print(callback()) } fun four(callbackOne: (String) -> Unit, callbackTwo: (String) -> Unit) { // возвращает 2 коллбека callbackOne("6 ") callbackTwo("7 ") } // 1 2 3 4 5 6 7 8 9 10
Функциональные типы:
Kotlin использует семейство функциональных типов, таких как (Int) -> String, для объявлений, которые являются частью функций: val onClick: () -> Unit = ….
У всех функциональных типов есть список с типами параметров, заключенный в скобки, и возвращаемый тип: (A, B) -> C обозначает тип, который предоставляет функции два принятых аргумента типа A и B, а также возвращает значение типа C. Список с типами параметров может быть пустым, как, например, в () -> A. Возвращаемый тип Unit не может быть опущен.
У функциональных типов может быть дополнительный тип получатель (receiver), который указывается в объявлении перед точкой: тип A.(B) -> C описывает функции, которые могут быть вызваны для объекта-получателя A с параметром B и возвращаемым значением C. Литералы функций с объектом-приёмником часто используются вместе с этими типами.
Останавливаемые функции (suspending functions) принадлежат к особому виду функциональных типов, у которых в объявлении присутствует модификатор suspend, например, suspend () -> Unit или suspend A.(B) -> C.
Объявление функционального типа также может включать именованные параметры: (x: Int, y: Int) -> Point. Именованные параметры могут быть использованы для описания смысла каждого из параметров.
Чтобы указать, что функциональный тип может быть nullable, используйте круглые скобки: ((Int, Int) -> Int)?.
При помощи круглых скобок функциональные типы можно объединять: (Int) -> ((Int) -> Unit).
Стрелка в объявлении является правоассоциативной (right-associative), т.е. объявление (Int) -> (Int) -> Unit эквивалентно объявлению из предыдущего примера, а не ((Int) -> (Int)) -> Unit.
Вы также можете присвоить функциональному типу альтернативное имя, используя псевдонимы типов:
typealias ClickHandler = (Button, ClickEvent) -> Unit
Создание функционального типа:
Существует несколько способов получить экземпляр функционального типа:
Используя блок с кодом внутри функционального литерала в одной из форм:
лямбда-выражение: { a, b -> a + b },
анонимная функция: fun(s: String): Int { return s.toIntOrNull() ?: 0 }
Используя экземпляр пользовательского класса, который реализует функциональный тип в качестве интерфейса:
class IntTransformer: (Int) -> Int { override operator fun invoke(x: Int): Int = TODO() } val intFunction: (Int) -> Int = IntTransformer()