O que é Property Delegate em Kotlin?
Property Delegate (delegação de propriedade) é um mecanismo do Kotlin que permite delegar a lógica de leitura (get) e escrita (set) de uma propriedade para outro objeto. Em vez de implementar a lógica diretamente na propriedade, você usa a palavra-chave by para apontar para um objeto delegado que controla o comportamento.
Os delegates mais conhecidos da biblioteca padrão são lazy, observable e vetoable, mas você pode criar os seus próprios para qualquer necessidade.
Sintaxe básica
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 operações de get (e set, se for var) para o objeto a direita.
Como funciona internamente
Quando você escreve val x by delegate, o compilador gera código equivalente a:
// Você 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 também 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 padrão
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 prático: 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 Configurações(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 = Configurações(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 Configuração(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
- Inicialização 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.
- Validação automatica: verificar restricoes a cada escrita sem repetir código.
- Logging e auditoria: registrar acessos e modificacoes de propriedades.
Casos de Uso no Mundo Real
Persistencia transparente em Android: delegates que encapsulam SharedPreferences ou DataStore permitem que propriedades de uma classe de configuração leiam e escrevam automaticamente no armazenamento persistente. O desenvolvedor interage com a propriedade como se fosse um campo normal, sem se preocupar com a camada de persistencia.
Injecao de dependência em frameworks como Koin: o Koin usa property delegates (
by inject()eby viewModel()) para injetar dependências de forma preguicosa em Activities, Fragments e ViewModels. Isso elimina boilerplate de resolucao manual e torna o código mais declarativo.Binding de views em Android legado: antes do Jetpack Compose, bibliotecas como ViewBinding e ButterKnife usavam delegates para vincular views do XML a propriedades da Activity ou Fragment, evitando chamadas repetidas a
findViewById.Validacao e sanitizacao automatica de dados: em sistemas de formularios ou APIs, delegates customizados podem validar valores a cada escrita (rejeitando dados invalidos) ou sanitizar entradas (removendo espacos extras, normalizando formatos) de forma transparente para o restante do código.
Boas Praticas
- Use
lazycom o modo de thread safety adequado: em contextos single-thread (como a thread principal do Android), useLazyThreadSafetyMode.NONEpara evitar o overhead de sincronizacao desnecessaria. - Implemente as interfaces
ReadOnlyPropertyouReadWriteProperty: ao criar delegates customizados, implementar essas interfaces torna o contrato explicito e facilita a leitura e manutenção do código. - Evite efeitos colaterais pesados em
getValue: o getter de uma propriedade delegada e chamado toda vez que a propriedade e lida. operações caras (como acesso a disco ou rede) devem ser cacheadas internamente pelo delegate. - Prefira delegates a campos com lógica duplicada: se você perceber que várias propriedades da mesma classe possuem lógica identica de validacao, logging ou persistencia, extraia essa lógica para um delegate reutilizavel.
- Documente delegates customizados com exemplos de uso: como delegates alteram o comportamento implicito de propriedades, outros desenvolvedores precisam entender o que acontece por tras do
by. Um KDoc com exemplo e essencial.
Perguntas Frequentes
P: Qual a diferenca entre lazy e lateinit?
R: lazy e um delegate para propriedades val que inicializa o valor na primeira leitura e o cacheia. lateinit e um modificador para propriedades var que permite declarar a propriedade sem valor inicial, atribuindo-o depois. lazy garante que o valor e calculado exatamente uma vez; lateinit permite reatribuicao e não suporta tipos primitivos.
P: Posso usar property delegates com propriedades de nivel de arquivo (top-level)?
R: Sim. Property delegates funcionam em propriedades de classes, objetos, interfaces e também em propriedades top-level. Para propriedades top-level, o parametro thisRef do getValue/setValue sera null, já que não há um objeto proprietario.
P: Como faco para delegar uma propriedade a outra propriedade da mesma classe?
R: A partir do Kotlin 1.4, você pode usar a sintaxe val novoNome by ::nomeAntigo para delegar uma propriedade a outra usando referência de propriedade. Isso e útil para renomear propriedades mantendo compatibilidade com código existente.
P: Property delegates impactam a performance da aplicação?
R: O impacto e minimo para a maioria dos casos. O delegate adiciona uma camada de indirection (uma chamada de método extra para getValue/setValue), mas o JIT do JVM normalmente otimiza isso. O lazy com SYNCHRONIZED tem um custo de sincronizacao na primeira leitura, mas leituras subsequentes sao rapidas.
Erros comuns
Nao entender que lazy e thread-safe por padrão: o modo
SYNCHRONIZEDtem overhead. UseNONEquando thread safety não e necessária.Criar delegates com efeitos colaterais pesados no getValue: o getter e chamado toda vez que a propriedade e lida. Operações caras devem ser cacheadas.
Esquecer de implementar setValue para var: se o delegate não tem
setValue, ele só funciona comval.Confundir delegação de propriedade com delegação de classe:
class A by bdelega a interface de uma classe;val x by delegatedelega uma propriedade. São mecanismos diferentes.Usar lazy em propriedades que mudam:
lazye paraval. Se você precisa de inicialização tardia comvar, uselateinitouDelegates.notNull.
Termos relacionados
- lazy: delegate da biblioteca padrão para inicialização preguicosa.
- lateinit: alternativa ao delegate
notNullpara inicialização tardia. - Delegation: conceito mais amplo que inclui delegação de classes e interfaces.
- observable/vetoable: delegates para monitorar e controlar mudancas de valor.
- val/var: delegates para
valprecisam apenas degetValue; delegates paravarprecisam também desetValue. - KProperty: objeto de reflexao que representa a propriedade sendo delegada.
Property Delegates são um dos recursos mais elegantes do Kotlin, permitindo separar a lógica transversal (logging, persistencia, válidação) da lógica de negócio de forma limpa e reutilizavel. Dominar esse conceito abre portas para APIs expressivas e código sem boilerplate.