← Назад к вопросам

Как работает Delegated property?

2.0 Middle🔥 72 комментариев
#Kotlin основы

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм работы delegated property (делегированных свойств) в Kotlin

Delegated property (делегированное свойство) — это паттерн в Kotlin, который позволяет делегировать логику getters (геттеров) и setters (сеттеров) свойств отдельному объекту, называемому делегатом. Это мощный инструмент для реализации повторно используемой, кастомной логики доступа к полям класса без дублирования кода.

Базовый синтаксис и ключевое слово by

Делегированное свойство объявляется с помощью ключевого слова by, за которым следует экземпляр делегата:

class Example {
    var delegatedProperty: String by Delegate()
}

Здесь логика чтения и записи delegatedProperty будет управляться экземпляром класса Delegate.

Требования к делегату: интерфейсы ReadOnlyProperty и ReadWriteProperty

Чтобы объект мог выступать в роли делегата, он должен реализовать один из двух операторных методов (используя конвенции):

  1. Для read-only свойств (val): оператор getValue(thisRef, property).
  2. Для mutable свойств (var): операторы getValue(thisRef, property) и setValue(thisRef, property, value).
import kotlin.reflect.KProperty

class Delegate {
    // Для val
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "Значение из делегата для свойства '${property.name}'"
    }

    // Для var
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("Значение '$value' записано в свойство '${property.name}'")
    }
}
  • thisRef: Ссылка на объект-владелец свойства (может быть null).
  • property: Мета-информация о самом свойстве (например, его имя).
  • value: Новое значение для записи (в setValue).

Как это работает под капотом: преобразование компилятора

Компилятор Kotlin преобразует код с делегированным свойством. Примерно так:

Исходный код:

class MyClass {
    val myVal by Delegate()
    var myVar by Delegate()
}

Скомпилированный эквивалент (упрощённо):

class MyClass {
    private val myVal$delegate = Delegate()
    val myVal: String
        get() = myVal$delegate.getValue(this, ::myVal)

    private val myVar$delegate = Delegate()
    var myVar: String
        get() = myVar$delegate.getValue(this, ::myVar)
        set(value) { myVar$delegate.setValue(this, ::myVar, value) }
}

Компилятор создаёт скрытое поле для хранения экземпляра делегата и заменяет вызовы геттера и сеттера свойства на вызовы соответствующих методов у этого делегата.

Стандартные делегаты из Kotlin Standard Library

Kotlin предоставляет несколько полезных делегатов "из коробки":

  • lazy(): Инициализация только при первом обращении (только для val). По умолчанию потокобезопасна.

    val expensiveResource: Resource by lazy {
        println("Инициализируем...")
        Resource.load()
    }
    // При первом вызове expensiveResource выполнится лямбда.
    
  • observable(): Позволяет выполнять действие при изменении значения свойства.

    var name: String by Delegates.observable("<не указано>") { 
        prop, old, new -> 
        println("${prop.name} изменилось с '$old' на '$new'")
    }
    
  • vetoable(): Похож на observable, но позволяет отклонить изменение и оставить старое значение.

    var positiveNumber: Int by Delegates.vetoable(0) { _, old, new ->
        new >= 0 // Если new < 0, изменение не произойдёт, останется old
    }
    
  • Хранение в Map / MutableMap:

    class User(val map: Map<String, Any?>) {
        val name: String by map // Делегирует чтение к map["name"]
        val age: Int     by map // Делегирует чтение к map["age"]
    }
    val user = User(mapOf("name" to "Alice", "age" to 30))
    println(user.name) // Обращается к map["name"]
    

Преимущества использования делегированных свойств

  • Уменьшение дублирования кода (DRY): Общую логику (логирование, валидация, ленивая загрузка) можно вынести в отдельный класс-делегат и переиспользовать.
  • Читаемость: Намерения становятся ясными (например, by lazy сразу говорит о ленивой инициализации).
  • Инкапсуляция: Логика доступа к данным скрыта внутри делегата.
  • Мощность: Позволяет создавать сложные, декларативные механизмы для управления состоянием.

Практический пример: кастомный делегат для SharedPreferences в Android

class PreferenceDelegate<T>(
    private val preferences: SharedPreferences,
    private val key: String,
    private val defaultValue: T
) : ReadWriteProperty<Any, T> {

    override fun getValue(thisRef: Any, property: KProperty<*>): T {
        return when (defaultValue) {
            is String -> preferences.getString(key, defaultValue) as T
            is Int -> preferences.getInt(key, defaultValue) as T
            is Boolean -> preferences.getBoolean(key, defaultValue) as T
            else -> throw IllegalArgumentException("Тип не поддерживается")
        }
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
        with(preferences.edit()) {
            when (value) {
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
                else -> throw IllegalArgumentException("Тип не поддерживается")
            }.apply()
        }
    }
}

// Использование
class SettingsActivity(context: Context) {
    private val prefs = context.getSharedPreferences("app_settings", MODE_PRIVATE)

    var userName: String by PreferenceDelegate(prefs, "user_name", "")
    var notificationsEnabled: Boolean by PreferenceDelegate(prefs, "notifications", true)
    var userScore: Int by PreferenceDelegate(prefs, "score", 0)

    // Теперь обращение к свойствам автоматически читает/пишет в SharedPreferences
    fun updateUser() {
        userName = "Новое имя" // Автоматически сохраняется в prefs
        println(userScore) // Автоматически читается из prefs
    }
}

Таким образом, делегированные свойства — это не просто синтаксический сахар, а полноценный паттерн, который переносит ответственность за управление доступом к данным на специализированный объект, делая код более модульным, выразительным и легко поддерживаемым.

Как работает Delegated property? | PrepBro