O que e lateinit em Kotlin?
O modificador lateinit permite declarar uma propriedade var nao-nullable sem inicializa-la no momento da declaracao. Voce esta dizendo ao compilador: “eu garanto que vou inicializar essa propriedade antes de usa-la”. Isso e util quando a inicializacao depende de um framework (como injecao de dependencias) ou acontece em um metodo de ciclo de vida.
Sem lateinit, toda propriedade nao-nullable precisa ser inicializada no construtor ou na declaracao. Com lateinit, voce adia essa inicializacao para um momento posterior.
Sintaxe basica
class MinhaActivity {
lateinit var repositorio: Repositorio
fun onCreate() {
repositorio = Repositorio() // Inicializada depois da construcao
repositorio.carregar()
}
}
Sem lateinit, voce teria que usar nullable (Repositorio?) e lidar com verificacoes de null em todo lugar, ou inicializar com um valor dummy que nao faz sentido.
Regras do lateinit
O lateinit tem restricoes especificas:
class Exemplo {
// OK: var + tipo nao-nullable + nao-primitivo
lateinit var nome: String
// ERRO: val nao pode ser lateinit (precisa ser var)
// lateinit val constante: String
// ERRO: tipos primitivos nao podem ser lateinit
// lateinit var contador: Int
// ERRO: tipo nullable nao faz sentido com lateinit
// lateinit var opcional: String?
}
Resumo das regras:
- Deve ser
var(naoval). - Deve ser tipo nao-nullable.
- Nao pode ser tipo primitivo (
Int,Long,Boolean, etc.). - Nao pode ter getter ou setter customizado.
- Pode ser usada em propriedades de classe ou top-level.
Verificando inicializacao com isInitialized
A partir do Kotlin 1.2, voce pode verificar se uma propriedade lateinit ja foi inicializada:
class Servico {
lateinit var conexao: Conexao
fun verificar() {
if (::conexao.isInitialized) {
println("Conexao ativa: $conexao")
} else {
println("Conexao ainda nao foi estabelecida")
}
}
fun desconectar() {
if (::conexao.isInitialized) {
conexao.fechar()
}
}
}
A sintaxe ::propriedade.isInitialized usa referencia de propriedade. Isso e util em metodos de limpeza ou quando a inicializacao e condicional.
lateinit em Android
O caso de uso mais classico de lateinit e em Activities e Fragments do Android:
class UsuarioActivity : AppCompatActivity() {
lateinit var binding: ActivityUsuarioBinding
lateinit var viewModel: UsuarioViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUsuarioBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this)[UsuarioViewModel::class.java]
configurarUI()
}
private fun configurarUI() {
binding.botaoSalvar.setOnClickListener {
viewModel.salvar()
}
}
}
Aqui, binding e viewModel nao podem ser inicializados no construtor porque dependem do ciclo de vida da Activity. O lateinit e a solucao natural.
lateinit com injecao de dependencias
Frameworks como Dagger, Hilt e Koin frequentemente usam lateinit para injecao:
class MeuServico {
@Inject
lateinit var repositorio: Repositorio
@Inject
lateinit var logger: Logger
fun executar() {
logger.info("Executando...")
repositorio.processar()
}
}
O framework de DI preenche as propriedades apos a construcao do objeto, antes de qualquer metodo de negocio ser chamado.
lateinit vs lazy
Ambos adiam a inicializacao, mas de formas diferentes:
class Comparacao {
// lateinit: inicializacao explicita, var, mutavel
lateinit var valorLateInit: String
// lazy: inicializacao automatica na primeira leitura, val, imutavel
val valorLazy: String by lazy {
println("Inicializando lazy...")
"Valor calculado"
}
fun inicializar() {
valorLateInit = "Valor definido"
}
}
fun main() {
val obj = Comparacao()
// lazy: inicializa automaticamente na primeira leitura
println(obj.valorLazy) // Imprime "Inicializando lazy..." e "Valor calculado"
// lateinit: voce controla quando inicializar
obj.inicializar()
println(obj.valorLateInit)
}
Quando usar cada um:
lateinit: quando a inicializacao depende de um fator externo (DI, ciclo de vida, framework).lazy: quando o valor pode ser calculado a partir de informacoes ja disponiveis, mas voce quer adiar o calculo.
O que acontece se acessar antes de inicializar
class Perigo {
lateinit var dados: String
fun acessar() {
println(dados) // UninitializedPropertyAccessException!
}
}
fun main() {
val obj = Perigo()
obj.acessar() // kotlin.UninitializedPropertyAccessException:
// lateinit property dados has not been initialized
}
A excecao UninitializedPropertyAccessException e lancada com uma mensagem clara indicando qual propriedade nao foi inicializada. E mais informativa que um NullPointerException.
lateinit em testes
lateinit e muito comum em classes de teste:
class CalculadoraTest {
lateinit var calculadora: Calculadora
lateinit var logger: MockLogger
@BeforeEach
fun setup() {
logger = MockLogger()
calculadora = Calculadora(logger)
}
@Test
fun `soma dois numeros`() {
val resultado = calculadora.somar(2, 3)
assertEquals(5, resultado)
}
@Test
fun `registra operacao no log`() {
calculadora.somar(2, 3)
assertTrue(logger.mensagens.contains("Soma: 2 + 3 = 5"))
}
}
O metodo @BeforeEach inicializa as propriedades antes de cada teste, garantindo um estado limpo.
Quando usar lateinit
- Injecao de dependencias: quando frameworks preenchem propriedades apos a construcao.
- Ciclo de vida: em Android, quando propriedades dependem de
onCreateouonViewCreated. - Testes: para inicializar objetos em metodos
@BeforeEachou@Before. - Configuracao tardia: quando a inicializacao depende de dados que chegam depois da construcao.
Erros comuns
Acessar antes de inicializar: o erro mais obvio. Sempre garanta que a propriedade e inicializada antes do primeiro acesso. Use
isInitializedquando houver duvida.Usar lateinit quando lazy e melhor: se o valor pode ser calculado a partir de dados ja disponiveis,
lazye mais seguro porque e automatico e imutavel.Usar lateinit para contornar null safety: declarar
lateinitso para evitar?e verificacoes de null e um mau uso. Se o valor pode legitimamente ser null, useString?.Lateinit em propriedades que nunca sao reinicializadas: se a propriedade e definida uma unica vez e nunca muda, considere
lazycomvalpara garantir imutabilidade.Nao tratar o caso de nao-inicializacao em cleanup: em metodos como
onDestroy, verificarisInitializedantes de acessar propriedades que podem nao ter sido inicializadas (ex: seonCreatefalhou).
Termos relacionados
- val: variavel somente leitura que nao pode ser usada com lateinit.
- var: variavel mutavel, requisito para lateinit.
- lazy: delegacao de propriedade que inicializa na primeira leitura, alternativa ao lateinit.
- Nullable: tipos que aceitam null (
String?), alternativa ao lateinit quando null e um estado valido. - Property Delegate: mecanismo que
lazyusa internamente, permitindo logica customizada de leitura e escrita. - Dependency Injection: padrao de design onde lateinit e frequentemente usado para receber dependencias.
O lateinit e uma ferramenta pragmatica que resolve situacoes reais onde a inicializacao no construtor nao e possivel. Usado com disciplina, ele mantem o codigo limpo e livre de verificacoes de null desnecessarias, enquanto a verificacao em tempo de execucao garante que erros de inicializacao sejam detectados cedo com mensagens claras.