Функции-расширения (extension functions) — одно из ключевых отличий Kotlin от Java, впервые появившееся с первых версий языка под вдохновением от C#. Они позволяют добавлять новые методы к существующим классам без необходимости наследования или модификации исходного класса. Это резко увеличивает выразительность и лаконичность кода.
В Java и многих других языках, невозможно добавить методы к уже существующему классу (к примеру, String или List) без использования наследования или паттерна Decorator. Kotlin решает этот недостаток через механизм extension functions.
Многие классы библиотеки (например, String, Int, List) невозможно менять напрямую. Часто требуются дополнительные методы, чтобы сделать код читаемее и избежать дублирования логики. Однако, extension functions не изменяют реальный класс — они создаются статически, и внутри не имеют доступа к private/protected членам.
В Kotlin, объявление extension function выглядит так:
fun String.lastChar(): Char = this[this.length - 1]
Теперь можно вызывать этот метод у любого String:
"Kotlin".lastChar() // вернёт 'n'
Extension-функция может быть переопределена в наследнике класса?
Нет. Extension-функции не являются членами класса, их нельзя переопределять через override: их статичность проявляется даже для наследников — компилятор выбирает функцию по типу выражения, а не по реальному типу объекта.
open class Base class Derived : Base() fun Base.foo() = "base" fun Derived.foo() = "derived" fun printFoo(b: Base) { println(b.foo()) } val d = Derived() printFoo(d) // выведет "base"
Могут ли extension-функции менять состояние объекта?
Extension-функции не могут получить доступ к private/protected состоянию, однако если тип muttable (например, List или свой класс), они могут изменять доступное внешнее состояние через публичные методы.
fun MutableList<Int>.addTwice(elem: Int) { add(elem) add(elem) }
Extension-функции конфликтуют с членами класса с таким же именем?
Если имя extension совпадает с именем настоящего метода, приоритет остаётся за членом класса. Extension будет вызван только если члены отсутствуют или не видны.
class Foo { fun bar() = "member" } fun Foo.bar() = "extension" val f = Foo() println(f.bar()) // "member"
Developer расширяет стандартный String функцией, задающей специфичный для проекта формат, и использует её повсеместно — при этом другие разработчики не замечают, что это не стандартная операция класса String.
Плюсы:
Минусы:
Расширение стандартного класса utility-методом, который точно локализован в пакете utils и хорошо документирован. Использование ограничено явно оговорёнными участками кода.
Плюсы:
Минусы: