extension function — механизм Kotlin, позволяющий добавлять новые методы к существующим классам без наследования и без изменения исходного кода. Функция объявляется с префиксом в виде типа-получателя и вызывается как обычный метод этого типа. По данным Kotlin Documentation, 2025, extension functions компилируются в статические методы с первым параметром-получателем, что гарантирует нулевой overhead в рантайме по сравнению с обычными вызовами.
Главное
extension function — это функция, объявленная вне класса, но вызываемая как его метод. Она принимает тип-получатель, к которому добавляется новый метод. Внутри функции ключевое слово this ссылается на экземпляр receiver-типа. Такой подход позволяет расширять финальные классы, классы сторонних библиотек и даже примитивные типы без создания обёрток.
В отличие от Java, где для добавления метода к существующему классу требуется либо наследование, либо статический утилитарный класс, Kotlin предлагает элегантное решение без boilerplate. По данным исследования Kotlin Foundation (2024), extension functions входят в пятёрку самых используемых возможностей языка — их применяют 78% Kotlin-разработчиков в повседневной работе.
Компилятор Kotlin транслирует extension function в статический метод, где первым аргументом передаётся экземпляр receiver. Это означает, что extension functions не изменяют байт-код целевого класса и не нарушают инкапсуляцию — доступны только публичные поля и методы receiver.
Используйте extension functions для написания чистых утилитарных функций, которые логически привязаны к определённому типу, но не могут быть добавлены в его исходный код.
Базовый синтаксис extension function: имя типа-получателя, точка, имя функции, затем параметры и возвращаемый тип. Внутри функции receiver доступен через this.
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Called as regular String method
val result = "user@example.com".isEmail() // true
В этом примере isEmail() становится доступным для всех строк. Внутри функции this — это сама строка, для которой вызывается метод. Kotlin позволяет опускать this в большинстве случаев — можно писать просто contains("@") вместо this.contains("@").
Extension functions могут принимать дополнительные параметры и возвращать значения любого типа. Это делает их полноценными функциями, а не просто синтаксическим сахаром.
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
if (this.isEmpty()) return listOf(default)
return this
}
val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)
Параметр default определяет значение, возвращаемое для пустого списка. Extension function может быть generic — тип List
Extension functions в Kotlin используют статическую диспетчеризацию, а не виртуальную. Это ключевое отличие от обычных методов классов. Какая extension function будет вызвана, определяется на этапе компиляции по статическому типу переменной, а не по её фактическому типу в рантайме.
open class Animal
class Dog : Animal()
fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof"
fun test() {
val animal: Animal = Dog()
println(animal.speak()) // "Animal sound" — static type Animal
val dog: Dog = Dog()
println(dog.speak()) // "Woof" — static type Dog
}
Несмотря на то, что переменная animal указывает на экземпляр Dog, вызывается extension function для Animal, потому что статический тип переменной — Animal. Если бы speak() был виртуальным методом класса, вызвался бы Dog.speak(). Это свойство важно учитывать при проектировании API на extension functions.
Extension functions не являются членами класса — это обычные функции, которые требуют импорта для использования в другом файле. Kotlin предоставляет два варианта импорта: по имени функции или с переименованием.
// Import by name
import com.example.extensions.isEmail
// Import with alias (to resolve conflicts)
import com.example.extensions.isEmail as isValidEmail
// Call after import
val valid = "test@test.com".isEmail()
Переименование через as полезно при конфликтах имён — например, если две библиотеки предоставляют extension function с одинаковым названием для одного типа. В этом случае можно импортировать одну из них с другим именем и вызывать через новое имя.
Extension functions можно объявлять на разных уровнях:
| Уровень | Видимость | Пример |
|---|---|---|
| Top-level | Весь проект после импорта | fun String.isEmail() |
| Member extension | Внутри класса-владельца | class A { fun B.ext() } |
| Local | Внутри функции | fun test() { fun String.ext() } |
Member extension functions — особый случай, когда extension function объявляется внутри другого класса. В этом случае внутри extension function доступны как receiver (this функции), так и члены внешнего класса.
Если у класса есть метод с той же сигнатурой, что и extension function, всегда вызывается член класса. Extension function никогда не переопределяет метод класса — это архитектурное решение для предотвращения случайных переопределений.
class User {
fun greet() = "Hello from class"
}
fun User.greet() = "Hello from extension"
fun main() {
val user = User()
println(user.greet()) // "Hello from class"
}
Даже если extension function определена позже и имеет ту же сигнатуру, компилятор выберет метод класса. Компилятор Kotlin выводит предупреждение при обнаружении такой ситуации. Единственный способ вызвать extension function при наличии метода класса — обратиться к ней как к обычной функции: greet(user).
В Android-разработке extension functions стали стандартным инструментом для работы с View, Context и фрагментами. Библиотека Android KTX построена на extension functions, предоставляя удобные обёртки над API Android.
// Extension function for working with View
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// Extension function for Context — toast
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// Extension function as DSL builder
fun String.colorFormat(color: String): String =
"$color$this\u001b[0m"
Extension function toast() делает Context-зависимые вызовы лаконичными: вместо написания Toast.makeText(context, message, length).show() достаточно написать context.toast("Текст"). Это уменьшает boilerplate и делает код более читаемым.
Часто задаваемые вопросы
Нет, extension functions используют статическую диспетчеризацию. Если объявить extension function для базового класса и такую же для подкласса, какая из них вызовется — определяется статическим типом переменной на этапе компиляции, а не фактическим типом в рантайме.
Можно объявлять extension functions для nullable receiver-типов: fun String?.isNullOrEmail(). Внутри такой функции this может быть null, поэтому необходимо использовать операторы безопасного вызова или явную проверку на null.
Нет, extension functions компилируются в статические методы. На уровне байт-кода вызов extension function идентичен вызову статического метода с первым параметром-получателем. Никакого рефлекшена или динамической диспетчеризации.
Да, extension properties работают аналогично extension functions, но не могут хранить состояние — только геттер и сеттер. Например: val List
В Java вы пишете StringUtils.isEmail(str), а в Kotlin — str.isEmail(). Разница не только в синтаксисе: extension functions поддерживают автодополнение в IDE, улучшают читаемость цепочек вызовов и позволяют IDE предлагать релевантные функции для конкретного типа. В Java таких возможностей нет.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также