O que e Property Delegate em Kotlin?
Property Delegate (delegacao de propriedade) e um mecanismo do Kotlin que permite delegar a logica de leitura (get) e escrita (set) de uma propriedade para outro objeto. Em vez de implementar a logica diretamente na propriedade, voce usa a palavra-chave by para apontar para um objeto delegado que controla o comportamento.
Os delegates mais conhecidos da biblioteca padrao sao lazy, observable e vetoable, mas voce pode criar os seus proprios para qualquer necessidade.
Sintaxe basica
class Exemplo {
// Delegando para lazy: inicializa na primeira leitura
val configuracao: String by lazy {
println("Calculando...")
"Valor configurado"
}
}
fun main() {
val obj = Exemplo()
println(obj.configuracao) // Imprime "Calculando..." e "Valor configurado"
println(obj.configuracao) // Imprime apenas "Valor configurado" (ja calculado)
}
A palavra-chave by indica que a propriedade esta delegando suas operacoes de get (e set, se for var) para o objeto a direita.
Como funciona internamente
Quando voce escreve val x by delegate, o compilador gera codigo equivalente a:
// Voce escreve:
class Exemplo {
val x: String by MeuDelegate()
}
// O compilador gera algo como:
class Exemplo {
private val x_delegate = MeuDelegate()
val x: String
get() = x_delegate.getValue(this, ::x)
}
Para propriedades var, o compilador tambem gera o setter:
// var y by delegate gera:
var y: String
get() = y_delegate.getValue(this, ::y)
set(value) { y_delegate.setValue(this, ::y, value) }
Delegates da biblioteca padrao
lazy
Inicializa o valor na primeira leitura e cacheia para acessos subsequentes:
val dadosPesados: List<String> by lazy {
println("Carregando dados pesados...")
carregarDoBanco()
}
Modos de thread safety do lazy:
// Thread-safe (padrao): sincronizado
val a by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { calcular() }
// Sem sincronizacao: mais rapido, mas nao thread-safe
val b by lazy(LazyThreadSafetyMode.NONE) { calcular() }
// Publication: permite calculo duplicado, mas garante que todos veem o mesmo valor
val c by lazy(LazyThreadSafetyMode.PUBLICATION) { calcular() }
observable
Notifica quando o valor muda:
import kotlin.properties.Delegates
var nome: String by Delegates.observable("Inicial") { propriedade, valorAntigo, valorNovo ->
println("${propriedade.name} mudou de '$valorAntigo' para '$valorNovo'")
}
fun main() {
nome = "Ana" // Imprime: nome mudou de 'Inicial' para 'Ana'
nome = "Bruno" // Imprime: nome mudou de 'Ana' para 'Bruno'
}
vetoable
Permite rejeitar mudancas de valor:
var idade: Int by Delegates.vetoable(0) { _, _, novoValor ->
novoValor >= 0 // So aceita valores nao-negativos
}
fun main() {
idade = 25 // Aceito
println(idade) // 25
idade = -5 // Rejeitado
println(idade) // 25 (nao mudou)
}
notNull
Similar ao lateinit, mas funciona com tipos primitivos delegados:
var contador: Int by Delegates.notNull<Int>()
fun main() {
// println(contador) // IllegalStateException!
contador = 42
println(contador) // 42
}
Criando um delegate customizado
Para criar um delegate, implemente os operadores getValue e opcionalmente setValue:
import kotlin.reflect.KProperty
class LoggingDelegate<T>(private var valor: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("Lendo ${property.name}: $valor")
return valor
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, novoValor: T) {
println("Escrevendo ${property.name}: $valor -> $novoValor")
valor = novoValor
}
}
class Usuario {
var nome: String by LoggingDelegate("Sem nome")
var idade: Int by LoggingDelegate(0)
}
fun main() {
val u = Usuario()
u.nome = "Ana" // Escrevendo nome: Sem nome -> Ana
println(u.nome) // Lendo nome: Ana
u.idade = 30 // Escrevendo idade: 0 -> 30
}
Exemplo pratico: SharedPreferences delegate
Um caso de uso real no Android e delegar propriedades para SharedPreferences:
class PreferenceDelegate(
private val prefs: SharedPreferences,
private val chave: String,
private val padrao: String
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return prefs.getString(chave, padrao) ?: padrao
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, valor: String) {
prefs.edit().putString(chave, valor).apply()
}
}
fun SharedPreferences.string(chave: String, padrao: String = "") =
PreferenceDelegate(this, chave, padrao)
// Uso
class Configuracoes(prefs: SharedPreferences) {
var tema: String by prefs.string("tema", "claro")
var idioma: String by prefs.string("idioma", "pt-BR")
var nomeUsuario: String by prefs.string("nome_usuario", "")
}
fun main() {
val config = Configuracoes(obterPrefs())
config.tema = "escuro" // Salva automaticamente no SharedPreferences
println(config.tema) // Le automaticamente do SharedPreferences
}
Delegate para Map
Kotlin tem suporte embutido para delegar propriedades a um Map:
class Usuario(mapa: Map<String, Any?>) {
val nome: String by mapa
val idade: Int by mapa
val email: String by mapa
}
fun main() {
val dados = mapOf(
"nome" to "Ana",
"idade" to 30,
"email" to "ana@email.com"
)
val usuario = Usuario(dados)
println(usuario.nome) // Ana
println(usuario.idade) // 30
}
Para propriedades mutaveis, use MutableMap:
class Configuracao(mapa: MutableMap<String, Any?>) {
var tema: String by mapa
var fontSize: Int by mapa
}
Interfaces ReadOnlyProperty e ReadWriteProperty
Para maior clareza, seus delegates podem implementar interfaces oficiais:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class ValidatedString(private var valor: String) : ReadWriteProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String = valor
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
require(value.isNotBlank()) { "${property.name} nao pode ser vazio" }
valor = value
}
}
class Formulario {
var nome: String by ValidatedString("")
var email: String by ValidatedString("")
}
Quando usar Property Delegates
- Inicializacao preguicosa:
lazypara valores calculados sob demanda. - Observacao de mudancas:
observableevetoablepara reagir a alteracoes de estado. - Persistencia transparente: delegar para SharedPreferences, banco de dados ou cache.
- Validacao automatica: verificar restricoes a cada escrita sem repetir codigo.
- Logging e auditoria: registrar acessos e modificacoes de propriedades.
Erros comuns
Nao entender que lazy e thread-safe por padrao: o modo
SYNCHRONIZEDtem overhead. UseNONEquando thread safety nao e necessaria.Criar delegates com efeitos colaterais pesados no getValue: o getter e chamado toda vez que a propriedade e lida. Operacoes caras devem ser cacheadas.
Esquecer de implementar setValue para var: se o delegate nao tem
setValue, ele so funciona comval.Confundir delegacao de propriedade com delegacao de classe:
class A by bdelega a interface de uma classe;val x by delegatedelega uma propriedade. Sao mecanismos diferentes.Usar lazy em propriedades que mudam:
lazye paraval. Se voce precisa de inicializacao tardia comvar, uselateinitouDelegates.notNull.
Termos relacionados
- lazy: delegate da biblioteca padrao para inicializacao preguicosa.
- lateinit: alternativa ao delegate
notNullpara inicializacao tardia. - Delegation: conceito mais amplo que inclui delegacao de classes e interfaces.
- observable/vetoable: delegates para monitorar e controlar mudancas de valor.
- val/var: delegates para
valprecisam apenas degetValue; delegates paravarprecisam tambem desetValue. - KProperty: objeto de reflexao que representa a propriedade sendo delegada.
Property Delegates sao um dos recursos mais elegantes do Kotlin, permitindo separar a logica transversal (logging, persistencia, validacao) da logica de negocio de forma limpa e reutilizavel. Dominar esse conceito abre portas para APIs expressivas e codigo sem boilerplate.