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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також