Late Init

Late Init

Observação: O arquivo utilizado neste conteúdo que fala sobre Late Init é o 8-lateinit.kt.

Pronto para mais um conteúdo?

Inicialização Tárdia

Existirão momentos em que precisamos inicializar algum atributo de maneira tardia, ou seja, por meio de outros métodos existentes na própria classe.

Vamos imaginar que temos uma classe chamada Receita, onde dentro dela temos um atributo chamado instrucoes, que será inicializado posteriormente:

class Receita{
lateinit var instrucoes: String//É como se você falasse: "Olha Kotlin, essa variável será inicializada posteriormente...", e esse posteriormente depende exclusivamente do seu código!
}

Tenha em mente que lateinit precisa ser do tipo VAR, pois VAL não conseguimos atribuir um valor posterior, pois como vimos, ele não pode ter seu valor alterado.

Caso tentarmos ter acesso a esse atributo, o compilador retornará um erro, alegando que a variável não foi inicializada:

var r: Receita = Receita()//Aqui estamos instanciando a classe Receita
println(r.instrucoes)//nesse momento teremos um problema, pois estamos tentando acessar de forma direta, um atributo que ainda não foi inicializado

Como o próprio nome já diz, Inicialização Tardia, o que significa que precisamos inicializar a variável posteriormente no nosso código, e nós podemos criar um método para isso:

class Receita{
lateinit var instrucoes: String//É como se você falasse: "Olha Kotlin, essa variável será inicializada posteriormente...", e esse posteriormente depende exclusivamente do seu código!

fun geraReceita(){//Dentro da função geraReceita, nós estamos inincializando a variável instrucoes com um texto do tipo STRING:
this.instrucoes = "Pegue 2 ovos, coloque numa bandeja, adicione sal e tome puro!"
}
}

Com a função acima, estamos inicializando nosso atributo.

Mas e se existisse um outro método responsável por imprimir essa receita? De modo que precisasse da variável instrucoes?

class Receita{

lateinit var instrucoes: String//É como se você falasse: "Olha Kotlin, essa variável será inicializada posteriormente...", e esse posteriormente depende exclusivamente do seu código!

fun geraReceita(){//Dentro da função geraReceita, nós estamos inincializando a variável instrucoes com um texto do tipo STRING:
this.instrucoes = "Pegue 2 ovos, coloque numa bandeja, adicione sal e tome puro!"
}

fun imprimeReceita(){
println(this.instrucoes)//Gera um erro se pularmos o 'geraReceita'
}

}

Se pularmos o geraReceita, de modo a instanciar a classe e chamar diretamente o método imprimeReceita, como acontece abaixo:

var r: Receita = Receita()//Aqui estamos instanciando a classe Receita
r.imprimeReceita()

O compilador gera um erro, alegando que estamos tentando acessar um atributo não inicializado.

Claro que podemos resolver isso chamando o geraReceita primeiro:

var r: Receita = Receita()//Aqui estamos instanciando a classe Receita
r.geraReceita()//Aqui estamos finalmente chamando o método que inicializa nosso atributo
r.imprimeReceita()//Por fim, conseguimos imprimir nossa receita: 'Pegue 2 ovos, coloque numa bandeja, adicione sal e tome puro!'

Mas será que existe uma forma de verificar se um atributo do tipo lateinit já foi inicializado?

Sim, e para isso podemos usar o isInitialized da seguinte forma:

fun imprimeReceita(){
//Segundo a lógica do nosso código, para imprimir uma receita, precisamos gerar primeiro, certo?
//Sabendo disso, não conseguiremos acessar a variável instrucoes sem antes iniciar ela. Pois caso façamos isso, o compilador gerará um erro.

//Sendo assim, será que existe como verificar se uma variável do tipo lateinit já foi inicializada?
//Sim, vejamos:

if(this::instrucoes.isInitialized){//Usamos o atributo chamado 'isInitializedpara verificar se a variável instrucoes já foi inicializada ou não. (Retorna Booleano)
println(this.instrucoes)//Caso sim, ele imprime a receita
}

println("Você precisa gerar a receita primeiro")//Caso não, alegamos que é necessário chamar o método geraReceita
}

Dessa forma, se chamarmos o imprimeReceita antes do geraReceita, o retorno será uma mensagem em vez de um erro:

var r: Receita = Receita()//Aqui estamos instanciando a classe Receita
r.imprimeReceita()//mostra 'Você precisa gerar a receita primeiro'

Uso real do Late Init?

E qual é a diferença entre usar o lateinit em uma variável, do que instanciar o atributo já com um valor padrão, ou quem sabe nulo?

Ou seja, porque ao invés de usar isso:

lateinit var instrucoes: String

Não escolhemos um desses dois:

var instrucoes: String = ""

...

var instrucoes: String?= null

E mais tarde, quando formos imprimir a receita, não fazermos pela lógica abaixo?

if(this.intrucoes != null && this.instrucoes != ""){
println(this.instrucoes)
}

println("Você precisa gerar a receita primeiro")

Resposta: Quando existe uma variável do tipo lateinit, é exigido que ela seja do tipo VAR, ao mesmo tempo que o compilador não se preocupa em armazená-la na memória, pois ela ainda não existe (e será inicializada tardiamente).

Se tratando da questão de performance das nossas aplicações, usar o late init nos tras uma certa vantagem do que declarar variáveis com valores pré-definidos.

Conclusão

Neste conteúdo você aprendeu a como declarar atributos do tipo lateinit e suas principais diferenças na performance das nossas aplicações.

Até o próximo conteúdo 😋