extension property — механизм Kotlin, позволяющий добавлять новые свойства к существующим классам без наследования и без изменения исходного кода. В отличие от extension functions, properties-расширения не могут хранить состояние — они объявляются только с геттером и, опционально, сеттером, поскольку не имеют backing field. По данным Kotlin Documentation, 2025, extension properties компилируются в статические методы getter и setter с receiver в качестве первого параметра.
Главное
extension property — это синтаксическая конструкция Kotlin, которая добавляет свойство к существующему типу без изменения его объявления. Свойство объявляется с указанием receiver-типа и обязательно содержит геттер. Ключевое отличие от обычных свойств — отсутствие backing field: extension property не может сохранять данные, а только вычисляет их на основе receiver-объекта.
По данным Kotlin Foundation Survey (2024), extension properties менее популярны, чем extension functions — их регулярно используют около 45% разработчиков. Это связано с ограничением на отсутствие состояния, что сужает область применения. Тем не менее, для вычисляемых свойств, логически привязанных к типу, extension properties — самый лаконичный вариант.
Extension properties компилируются в пару статических методов getter и setter. На уровне байт-кода нет разницы между вызовом extension property и вызовом extension function — оба становятся статическими методами с receiver-параметром. По данным JetBrains (Kotlin Docs, 2025), overhead отсутствует полностью.
Используйте extension properties для коротких вычисляемых значений, которые должны выглядеть как свойства, а не как вызовы методов — это улучшает читаемость кода и соблюдает принцип единообразного доступа.
Для объявления extension property используется синтаксис, похожий на обычное свойство, но с префиксом receiver-типа. val объявляет read-only extension property с обязательным геттером, var — mutable с геттером и опциональным сеттером.
// Read-only extension property
val String.isEmail: Boolean
get() = this.contains("@") && this.contains(".")
// Call
val valid = "test@test.com".isEmail
Обратите внимание: extension property вызывается без скобок — str.isEmail, а не str.isEmail(). Это и есть ключевое различие между extension property и extension function: property выглядит как поле, хотя на самом деле вычисляется через геттер.
Extension properties могут быть обобщёнными — receiver может использовать generic-параметры. Это позволяет создавать универсальные свойства, работающие с любым типом коллекции.
val List<T>.secondOrNull: T?
get() = if (size >= 2) this[1] else null
val items = listOf("a", "b", "c")
val second = items.secondOrNull // "b"
Свойство secondOrNull работает для любого типа T, возвращая второй элемент списка или null, если элементов меньше двух. Это типичный пример, где extension property уместнее функции — обращение выглядит как чтение поля.
Extension property не может иметь backing field, потому что она не добавляется в метаданные класса — она существует только как пара статических функций getter/setter. Backing field (ключевое слово field в Kotlin) — это внутреннее поле класса, которое хранит значение свойства. У extension property нет доступа к внутренней структуре класса.
// ❌ ERROR: extension property cannot have a backing field
var String.cachedValue: String
get() = "computed"
set(value) {
field = value // field is not accessible!
}
// ✅ CORRECT: use external storage
val cache = MutableMap<String, String>()
var String.cachedValue: String
get() = cache[this] ?: ""
set(value) { cache[this] = value }
Внешний Map в примере решает проблему хранения, но порождает другую — утечку памяти. Полученные через extension property значения живут в Map вечно, если их не очищать. Это ограничение делает extension property неподходящими для кэширования или хранения временных данных.
Для кэширования рекомендуется использовать WeakHashMap или механизмы с автоматической очисткой. JetBrains рекомендует избегать использования var extension properties с внешним хранилищем в production-коде без тщательного управления жизненным циклом.
Выбор между extension property и extension function зависит от семантики: свойство описывает характеристику объекта, а функция — действие. Принцип единообразного доступа (Uniform Access Principle) гласит: клиент не должен знать, вычисляется значение или хранится. Если значение можно представить как характеристику (длину, размер, статус) — используйте property.
| Критерий | Extension property | Extension function |
|---|---|---|
| Вызов | Без скобок: obj.property | Со скобками: obj.function() |
| Семантика | Характеристика, атрибут | Действие, операция |
| Backing field | Не поддерживается | Не применимо |
| Параметры | Только getter/setter | Любые параметры |
| Производительность | Одинаково (статический метод) | Одинаково (статический метод) |
| Пример | text.length | text.isEmail() |
Правило простое: если операция принимает параметры — используйте extension function. Если это простое вычисляемое значение без параметров — extension property. По данным Android Architecture Guide (Google, 2025), предпочтение следует отдавать extension properties для доступа к данным и extension functions для операций с побочными эффектами.
Extension property с ключевым словом var поддерживает сеттер, но без возможности хранить значение — сеттер обычно выполняет побочное действие или сохраняет данные во внешнем хранилище. Синтаксис аналогичен mutable свойствам классов.
// Mutable extension property with setter
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) {
this.setCharAt(length - 1, value)
}
val sb = StringBuilder("Kotlin")
println(sb.lastChar) // n
sb.lastChar = '!'
println(sb) // Kotli!
Свойство lastChar — классический пример из документации Kotlin. Геттер возвращает последний символ StringBuilder, сеттер заменяет его на новое значение. Обратите внимание: состояние хранится в самом StringBuilder (через setCharAt), а не в отдельном поле — это корректное использование extension property.
В реальных проектах extension properties чаще всего применяются для упрощения доступа к данным коллекций, для расчёта размеров или статусов UI-элементов, и для создания удобного API поверх существующих классов. Стандартная библиотека Kotlin активно использует этот механизм: size, indices, lastIndex для коллекций — это extension properties.
// Extension properties for collections
val List<Int>.sumFast: Int
get() = fold(0) { acc, i -> acc + i }
val String.half: String
get() = this.substring(0, length / 2)
// Extension property for Android View
val View.isVisible: Boolean
get() = visibility == View.VISIBLE
// Null check via safe receiver
val String?.isNullOrBlank: Boolean
get() = this == null || this.isBlank()
Extension property isVisible для View — пример, который стоит знать каждому Android-разработчику. Вместо view.visibility == View.VISIBLE можно писать view.isVisible. Это не только короче, но и читается как естественный язык: «если view видим». Несмотря на простоту, такие свойства существенно улучшают читаемость кода.
Часто задаваемые вопросы
Нет, extension properties не могут быть объявлены для companion object или object declaration. Механизм extension применим только к классам, интерфейсам и nullable-типам. Для object используйте обычные функции верхнего уровня.
Inline-свойство (с модификатором inline) — механизм Kotlin для вызова геттера/сеттера без создания объекта-свойства. Extension property всегда компилируется в статический метод, а inline-свойство — в вызов без обёртки. Они решают разные задачи: extension property добавляет свойство к существующему типу, inline — оптимизирует вызовы своих свойств.
Да, extension property может содержать аннотации, но только на уровне объявления. Нельзя аннотировать отдельно getter или setter extension property — в отличие от обычных свойств класса. Пример: @JvmName("getIsValid") val String.isValid get() = true.
Нет, extension properties нельзя объявить с companion object в качестве receiver. Это ограничение языка — extension property работает только с экземплярами типов, а companion object — статический контекст. Используйте extension функции верхнего уровня или константы.
Минимально. Каждое extension property добавляет один статический метод getter (и опционально setter) в скомпилированный байт-код. Для сравнения: создание класса-обёртки с таким же свойством добавляет целый класс. Extension properties — более лёгкий подход к расширению функциональности.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также