5. Понимание функций расширения
В этом задании вы узнаете о расширении поведения классов. Очень часто для расширения поведения класса пишутся вспомогательные функции. Kotlin предоставляет удобный синтаксис для объявления таких вспомогательных функций и называет их функциями расширения.
Функции расширения позволяют добавлять функции к существующему классу без необходимости доступа к его исходному коду. Например, вы можете объявить их в файле Extensions.kt , который является частью вашего пакета. Это фактически не изменяет класс, но позволяет использовать точечную нотацию при вызове функции для объектов этого класса.
Шаг 1: Напишите функцию расширения.
StringТип данных `a` — ценный объект в Kotlin, обладающий множеством полезных функций. Но что, если нам потребуется дополнительнаяStringфункциональность, недоступная напрямую? Например, мы можем захотеть определить,Stringсодержит ли объект `a` какие-либо пробелы.
Продолжая работу в REPL, напишите простую функцию расширения для Stringкласса, hasSpaces()которая будет проверять, содержит ли строка пробелы. Имя функции должно начинаться с префикса класса, с которым она работает.
fun String.hasSpaces(): Boolean {
val found = this.indexOf(' ')
// also valid: this.indexOf(" ")
// returns positive number index in String or -1 if not found
return found != -1
}
- Функцию можно упростить
hasSpaces(). Явноеthisуказание не требуется, и функцию можно свести к одному выражению и вернуть.
fun String.hasSpaces() = indexOf(" ") != -1
Шаг 2: Изучите ограничения расширений.
Функции расширения имеют доступ только к публичному API класса, от которого они наследуются. privateДоступ к членам класса, от которого они наследуются, закрыт.
- Попробуйте добавить функции расширения, которые вызывают свойство, помеченное как
private.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- Изучите приведенный ниже код и определите, что он выведет на экран.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()выводит GreenLeafyPlant. Можно было бы ожидать , aquariumPlant.print()что выведется GreenLeafyPlantи , поскольку ему было присвоено значение plant. Но тип определяется во время компиляции, поэтому AquariumPlantвыводится .
Шаг 3: Добавьте свойство расширения.
Помимо функций расширения, Kotlin также позволяет добавлять свойства расширения. Как и в функциях расширения, вы указываете класс, который расширяете, затем точку, а затем имя свойства.
- Продолжая работу в REPL, добавьте свойство расширения
isGreenкAquariumPlant, которое определяет,trueзеленый ли цвет.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
Доступ к свойству isGreenосуществляется так же, как и к обычному свойству; при обращении isGreenвызывается геттер для получения значения.
- Выведите значение
isGreenсвойства переменнойaquariumPlantи посмотрите на результат.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Шаг 4: Узнайте о получателях, допускающих обнуление.
Класс, который вы наследуете, называется приемником , и этот класс можно сделать допускающим значение null. В этом случае переменная, thisиспользуемая в теле метода, может иметь значение nullnull, поэтому обязательно проверьте это. Приемник с возможностью значения null следует использовать, если вы ожидаете, что вызывающие функции будут обращаться к вашему методу расширения с переменными, допускающими значение null, или если вы хотите обеспечить поведение по умолчанию при применении вашей функции к переменной null.
- Продолжая работу в REPL, определите
pull()метод, который принимает в качестве приемника значение, допускающее значение null. Это обозначается вопросительным знаком?после типа, перед точкой. Внутри тела метода вы можете проверить,thisне допускает ли значение nullnull, используя?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- В этом случае при запуске программы ничего не выводится. Поскольку
plantзначение равно нулюnull, внутренний методprintln()не вызывается.
Функции расширения обладают огромным потенциалом, и большая часть стандартной библиотеки Kotlin реализована в виде функций расширения.