Kotlin tomou o lugar do Java como linguagem oficial do Android e diversas grandes empresas vem migrando seus back-ends de Java para Kotlin. Neste post vou mostrar a você porquê isso vem acontecendo.
O poder do Kotlin
Kotlin é tão poderosa quanto o Java e isso é incrível. No post anterior eu falei um pouco sobre a interoperabilidade entre as duas e contei brevemente a história da linguagem criada pelos engenheiros da Jetbrains. Essa interoperabilidade, aliada ao quão concisa Kotlin é, a torna uma substituta perfeita para a mãe dos legados.
Vamos tomar como exemplo uma classe que representa uma conta corrente em Java e em Kotlin.
Em Java:
import java.util.Objects; | |
public class ContaCorrenteJava { | |
private String titular; | |
private String cpf; | |
private String agencia; | |
private String conta; | |
private Float saldo; | |
public ContaCorrenteJava( | |
String titular, | |
String cpf, | |
String agencia, | |
String conta, | |
Float saldo | |
) { | |
this.titular = titular; | |
this.cpf = cpf; | |
this.agencia = agencia; | |
this.conta = conta; | |
this.saldo = saldo; | |
} | |
public String getTitular() { | |
return this.titular; | |
} | |
public void setTitular(String titular) { | |
this.titular = titular; | |
} | |
public String getCpf() { | |
return this.cpf; | |
} | |
public void setCpf(String cpf) { | |
this.cpf = cpf; | |
} | |
public String getAgencia() { | |
return this.agencia; | |
} | |
public void setAgencia(String agencia) { | |
this.agencia = agencia; | |
} | |
public String getConta() { | |
return this.conta; | |
} | |
public void setConta(String conta) { | |
this.conta = conta; | |
} | |
public Float getSaldo() { | |
return this.saldo; | |
} | |
public void setSaldo(Float saldo) { | |
this.saldo = saldo; | |
} | |
public ContaCorrenteJava copy() { | |
return new ContaCorrenteJava( | |
this.titular, | |
this.cpf, | |
this.agencia, | |
this.conta, | |
this.saldo | |
); | |
} | |
@Override | |
public String toString() { | |
return "ContaCorrenteJava(titular=" + this.titular + | |
", cpf=" + this.cpf + | |
", agencia=" + this.agencia + | |
", conta=" + this.conta + | |
", saldo=" + this.saldo + | |
")"; | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (!(o instanceof ContaCorrenteJava)) return false; | |
ContaCorrenteJava that = (ContaCorrenteJava) o; | |
return Objects.equals( | |
getTitular(), | |
that.getTitular() | |
) && | |
Objects.equals( | |
getCpf(), | |
that.getCpf() | |
) && | |
Objects.equals( | |
getAgencia(), | |
that.getAgencia() | |
) && | |
Objects.equals( | |
getConta(), | |
that.getConta() | |
) && | |
Objects.equals(getSaldo(), | |
that.getSaldo() | |
); | |
} | |
@Override | |
public int hashCode() { | |
return | |
Objects.hash( | |
getTitular(), | |
getCpf(), | |
getAgencia(), | |
getConta(), | |
getSaldo() | |
); | |
} | |
} |
Em Kotlin:
data class ContaCorrenteKt( | |
var titular: String, | |
val cpf: String, | |
val agencia: String, | |
val conta: String, | |
var saldo: Float | |
) |
É isso mesmo, o código Kotlin acima faz exatamente o que o em Java faz 😱
A ideia da classe ContaCorrente é representar um conta com as seguintes propriedades: um titular, que pode ser mudado, um cpf, uma agência, uma conta, todos imutáveis e um saldo, mutável, é claro. Para construi essa classe em Java respeitando os princípios do encapsulamento, temos que desenvolver os getters e setters necessários, além de criar funções auxiliares como toString()
, copy()
, equals()
e hashCode()
. Mas para fazer isso em Kotlin basta criar uma data class, usar os declaradores de propriedades corretos (var
e val
) e pronto: temos todos os getters e setters necessários além das funções antes mencionadas!
Para verificarmos a veracidade disso, vamos analisar o código abaixo:
fun main() { | |
val ccKt = ContaCorrenteKt( | |
"Ronaldo", | |
"022.521.260-94", | |
"69624", | |
"246964", | |
10000.0f | |
) | |
val ccJava = ContaCorrenteJava( | |
"Ronaldo", | |
"022.521.260-94", | |
"69624", | |
"246964", | |
10000.0f | |
) | |
println(ccKt.titular) | |
ccKt.titular = "João" | |
println(ccKt.titular) | |
println(ccKt.cpf) | |
println(ccKt.agencia) | |
println(ccKt.conta) | |
println(ccKt.saldo) | |
ccKt.saldo += 2000.0f | |
println(ccKt.saldo) | |
println(ccKt.toString()) | |
println(ccKt.hashCode()) | |
println(ccKt.equals(ccJava)) | |
println() | |
println(ccJava.getTitular()) | |
ccJava.setTitular("João") | |
println(ccJava.getTitular()) | |
println(ccJava.getCpf()) | |
println(ccJava.getAgencia()) | |
println(ccJava.getConta()) | |
println(ccJava.getSaldo()) | |
ccJava.setSaldo(ccJava.getSaldo()+2000.0f) | |
println(ccJava.getSaldo()) | |
println(ccJava.toString()) | |
println(ccJava.hashCode()) | |
println(ccJava.equals(ccKt)) | |
val novaCcKt = ccKt.copy() | |
val novaCcJava = ccJava.copy() | |
println() | |
println(novaCcKt) | |
println(novaCcJava.toString()) | |
} |
Nesse programa Kotlin nós instanciamos as duas classes que representam uma conta corrente e chamamos todos os seus métodos para compararmos seus poderes de abstração. Repare que para acessar os getters e setters em Kotlin, basta usarmos a notação de .
. Eis a saída:
Ronaldo
João
022.521.260-94
69624
246964
10000.0
12000.0
ContaCorrenteKt(titular=João, cpf=70116672293, agencia=69624, conta=246964, saldo=12000.0)
152796811
false
Ronaldo
João
022.521.260-94
69624
246964
10000.0
12000.0
ContaCorrenteJava(titular=João, cpf=70116672293, agencia=69624, conta=246964, saldo=12000.0)
181425962
false
ContaCorrenteKt(titular=João, cpf=70116672293, agencia=69624, conta=246964, saldo=12000.0)
ContaCorrenteJava(titular=João, cpf=70116672293, agencia=69624, conta=246964, saldo=12000.0)
Como podemos ver, ambas tem o mesmo poder de abstração, mas a diferença de verbosidade entre as duas é gigante! E esse é um pequeno gostinho do porquê Kotlin é cada vez mais adotado quando comparada a Java.
Var e Val
Você deve ter reparado que usamos diversas vezes duas palavras-chaves muito importantes em Kotlin: var
e val
. E o que elas significam? Basicamente, essa é a notação que a linguagem usa para declarar propriedades que são mutáveis e aquelas que são imutáveis.
Ao declarar uma propriedade como var
(vem de variable), queremos dizer que ela é mutável e ganhamos de brinde os seus setter e getter de forma implícita, ela corresponde a uma variável em Java com seus métodos getter e setter.
Ao declarar uma propriedade como val
(vem de value), queremos dizer que ela é imutável e, é claro, já temos seu método getter implementado implicitamente. Desse modo, ela corresponde a uma variável final em Java com seu getter já desenvolvido.
É importante salientar que apesar das propriedades já virem com seus getters e setters implementados, não significa que nós não podemos customizá-los. Vamos implementar uma nova propriedade na nossa classe de conta corrente e criar um getter customizado. Essa propriedade representa a resposta para a pergunta: “a conta está negativada?”, true
para sim e false
para não. Dessa forma basta que o getter dessa propriedade seja a comparação entre o saldo da conta e zero:
data class ContaCorrenteKt( | |
var titular: String, | |
val cpf: String, | |
val agencia: String, | |
val conta: String, | |
var saldo: Float, | |
) { | |
val estaNegaticada: Boolean | |
get() = saldo < 0 | |
} |
E pronto: já temos um getter customizado! Agora para fazer um setter customizado vamos implementar um setter para a propriedade saldo. A lógica é a seguinte: o saldo só será alterado se o valor passado para o setter for positivo:
data class ContaCorrenteKt( | |
var titular: String, | |
val cpf: String, | |
val agencia: String, | |
val conta: String, | |
) { | |
val estaNegaticada: Boolean | |
get() = saldo > 0 | |
var saldo: Float = 0.0f | |
set(valor) { | |
if(valor > 0.0f) field = valor | |
} | |
} |
Feito. Repare que antes de implementar o setter, tivemos que inicializar a propriedade, isso foi necessário pois o setter usa o valor da propriedade para fazer a atribuição, por isso ela não pode ser nula. E quanto a palavra-chave field
, ela representa a própria propriedade, é algo parecido ao usar this.saldo
em Java.
Funções
Como já deve ter percebido, para se declarar uma função em Kotlin, usamos a palavra-chave fun
, pra você ver como é divertido programar em Kotlin 🤣. Além disso a função chamada main
é a função especial que indica onde o programa deve começar a ser executado. Agora vamos analisar de forma mais profunda a sintaxe das funções com um exemplo do Kotlin in Action:
Assim como em Java, o nome da função vem logo no início, nesse caso max
, depois entre parênteses temos seus parâmetros na forma <nome>: <Tipo>
, em seguida após os dois pontos o tipo de retorno da função e por fim, entre chaves, o seu corpo em si. Essa função compara dois valores e retorna o maior. Repare que em Kotlin o if
não é uma declaração (statement), mas sim uma expressão, ou seja, ela tem um valor, que está atrelado às suas comparações lógicas. No caso acima, se a
for maior que b
, o valor da expressão será a
, caso contrário será b
, e é por isso que a expressão pode ser retornada.
Obs: existe outra forma mais concisa de escrever a função max
em Kotlin:
fun max(a: Int, b: Int) = if(a > b) a else b |
O =
substitui o tipo de retorno da função, as chaves e a palavra-chave return
. Muito legal, né?
Null safety
Para fechar com chave de ouro vamos falar sobre uma das melhores qualidades do Kotlin: sua segurança. Você, pessoa desenvolvedora Java, quantos vezes já não se deparou com o maldito NullPointerException
? Pois saiba que com Kotlin seus problemas acabaram, ou quase isso.
Nessa tão querida linguagem, nós podemos declarar propriedades como nuláveis ou não-nuláveis (em português isso fica bem feio, mas em inglês é nullable e non-nullable), da seguinte forma:
val nome: String = "João" // non-nullable | |
val sobrenome: String? = "Martins" // nullable |
Para acessar a propriedade nome
, não temos nenhum problema, pois sabemos que ela não pode ser nula. Mas e quanto a propriedade `sobrenome? Em Java, seria necessário fazer o seguinte:
if(sobrenome != null) System.out.println(sobrenome.length());
Mas em Kotlin, nós temos uma feature muito útil chamada safe call
. De modo que o código acima pode ser reduzido ao seguinte:
println(sobrenome?.length)
Trata-se da notação ?.
, ela garante que estamos acessando uma propriedade que não está nula no momento, do contrário retorna apenas null. Essa notação é muito útil em chamadas encadeadas como o seguinte exemplo de kotlinlang.org:
bob?.department?.head?.name = managersPool.getManager()
Se qualquer uma das propriedade da cadeia for nula, essa atribuição é pulada e não considerada.
Mas e para o caso de eu querer fazer uma atribuição de uma propriedade non-nullable para uma propriedade nullable? Você poderia fazer o seguinte:
val l: Int = if (b != null) b.length else -1
Porém veja que mais uma vez precisamos de uma expressão inteira para uma simples atribuição. Foi pensando em solucionar isso que Kotlin tem o brilhante Elvis operator!
val l: Int = b?.length ?: -1
Nome criativo, não? Esse operador ?:
serve para avaliar se a expressão à esquerda é nula, caso ela não seja, ele a retorna, caso seja, ele retorna o operador à direita!
Agora, se mesmo com todas essas opções você ainda quiser ver um NullPointerException
, Kotlin deixa, basta usar o operador !!
:
val l = b!!.length
Nesse caso, se b
não for nulo, seu valor será retornado, mas caso seja, uma NullPointerException
será lançada!
Próximos posts
E por hoje é isso, pessoal! Conseguimos falar um pouco sobre Kotlin, mas faltam alguns conceitos importantes como as clássicas expressões for
, while
, uma expressão nova chamada when
, entre outras que vamos discutir no próximo post sobre Kotlin.
Obrigado pela atenção, até a próxima!
Post anterior:

Breve história do Kotlin
Ronaldo Costa de Freitas ・ Oct 25 '22 ・ 3 min read
Próximo post:
Top comments (0)