Открыть все вопросы по Spring
Открыть все вопросы по Android
Содержание:
➤ Что такое внутренние классы (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 } ?: defaultValuerun:
выполняет блок кода на объекте, переданном в качестве this, и возвращает результат этого блока. Внутри блока можно использовать ссылку на объект через this.
val result = someObject?.run { property } ?: defaultValuewith:
выполняет блок кода, который передает объект в качестве аргумента, и возвращает результат этого блока.
val result = with(someObject) { property } ?: defaultValueapply:
выполняет блок кода на объекте, переданном в качестве 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()