5. Понимание функций расширения

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

Функции расширения позволяют добавлять функции к существующему классу без необходимости доступа к его исходному коду. Например, вы можете объявить их в файле Extensions.kt , который является частью вашего пакета. Это фактически не изменяет класс, но позволяет использовать точечную нотацию при вызове функции для объектов этого класса.

Шаг 1: Напишите функцию расширения.

  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
}
  1. Функцию можно упростить hasSpaces(). Явное thisуказание не требуется, и функцию можно свести к одному выражению и вернуть.
fun String.hasSpaces() = indexOf(" ") != -1

Шаг 2: Изучите ограничения расширений.

Функции расширения имеют доступ только к публичному API класса, от которого они наследуются. privateДоступ к членам класса, от которого они наследуются, закрыт.

  1. Попробуйте добавить функции расширения, которые вызывают свойство, помеченное как 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'
  1. Изучите приведенный ниже код и определите, что он выведет на экран.
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 также позволяет добавлять свойства расширения. Как и в функциях расширения, вы указываете класс, который расширяете, затем точку, а затем имя свойства.

  1. Продолжая работу в REPL, добавьте свойство расширения isGreenк AquariumPlant, которое определяет, trueзеленый ли цвет.
val AquariumPlant.isGreen: Boolean
   get() = color == "green"

Доступ к свойству isGreenосуществляется так же, как и к обычному свойству; при обращении isGreenвызывается геттер для получения значения.

  1. Выведите значение isGreenсвойства переменной aquariumPlantи посмотрите на результат.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true

Шаг 4: Узнайте о получателях, допускающих обнуление.

Класс, который вы наследуете, называется приемником , и этот класс можно сделать допускающим значение null. В этом случае переменная, thisиспользуемая в теле метода, может иметь значение nullnull, поэтому обязательно проверьте это. Приемник с возможностью значения null следует использовать, если вы ожидаете, что вызывающие функции будут обращаться к вашему методу расширения с переменными, допускающими значение null, или если вы хотите обеспечить поведение по умолчанию при применении вашей функции к переменной null.

  1. Продолжая работу в REPL, определите pull()метод, который принимает в качестве приемника значение, допускающее значение null. Это обозначается вопросительным знаком ?после типа, перед точкой. Внутри тела метода вы можете проверить, thisне допускает ли значение null null, используя?.apply.
fun AquariumPlant?.pull() {
   this?.apply {
       println("removing $this")
   }
}

val plant: AquariumPlant? = null
plant.pull()
  1. В этом случае при запуске программы ничего не выводится. Поскольку plantзначение равно нулю null, внутренний метод println()не вызывается.

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


Last modified: Sunday, 5 April 2026, 2:16 PM