Iniciando com Kotlin

Leonardo Ferreira
7 min readDec 18, 2019

--

Minha ideia nesse post é mostrar o básico da linguagem, com mais código e menos teoria.

Mas se você chegou aqui, sem saber o que é Kotlin e quer uma base teórica antes de seguir, recomendo antes de continuar dar uma olhada na FAQ oficial da linguagem que está disponível em: https://kotlinlang.org/docs/reference/faq.html

Começando do começo

Nada melhor para começar em uma linguagem do que aprendendo a “fazer um olá mundo”, então:

fun main() {
println("hello world")
}

Podemos ver que o ponto de entrada do nosso código é a função main assim como em Java, por exemplo. Uma diferença um pouco maior, é o fato de não precisarmos de uma classe “em volta” da função main, e também não recebermos nenhum parâmetro, porém o segundo ponto é opcional, se precisarmos por algum motivo receber os argumentos que foram passados para o nosso programa, podemos fazer:

fun main(args: Array<String>) {
println("hello world")
}

O resultado nesse caso será exatamente o mesmo.

Tipos

Um ponto que é muito diferente de outras linguagens, é que normalmente temos:

Tipo nome

enquanto em Kotlin, temos:

nome: Tipo

e isso se aplica a praticamente tudo, no exemplo anterior, vimos que args é o nome do parâmetro que recebemos na função main e o tipo é um Array<String> .

Funções

Declarar uma função também não é uma tarefa difícil, só precisamos da keyword fun somado com o nome da função e os seus parâmetros, exemplo:

fun myFun() {
...
}

Assim como vimos que para os parâmetros da função os tipos são declarados a direita, o retorno da função segue a mesma ideia, então podemos declararmos uma função que retorna uma String da seguinte forma:

fun myFun(): String {
return "Hello World"
}

É importante dizer que quando não declaramos o tipo, temos uma função que retorna Unit, que é basicamente o void do Kotlin.

Para os casos como o exemplo acima, onde temos uma função com apenas uma instrução e essa instrução é o retorno, podemos declara-la de uma forma um pouco diferente:

fun myFun(): String = "Hello World"

Ainda nesse exemplo, podemos omitir o tipo de retorno e ele será inferido

fun myFun() = "Hello world"

Legal né!?

Mutável vs Imutável

Em Kotlin, podemos declarar variáveis e atributos com duas keywords, val ou var, quando fazemos:

fun main() {
val message = "Hello world"
println(message)
}

significa que a “variável” message, não trocará de valor. Por outro lado, podemos fazer:

fun main() {
var message = "Hello world"
message = "Olá mundo"

println(message)
}

E nesse caso message poderá trocar de valor.

Classes

Kotlin permite que nosso código possa seguir tanto o paradigma funcional quanto também o orientado a objeto.
Até agora vimos exemplos com funções e daqui pra frente começaremos a ver exemplos com classes e objetos.

A ideia de classe, objeto e interface é a mesma que em Java, porém o jeito que declaramos é um pouco diferente.

Interface:

interface Chair {
fun model(): String
}

Classe:

class GamerChair : Chair {
override fun model() =
"DT3"
}

E para criarmos uma instancia dessa classe:

fun main() {
val chair: Chair = GamerChair()
println("model=" + chair.model())
}

Podemos melhorar esse exemplo, utilizando string templates da seguinte forma:

fun main() {
val chair: Chair = GamerChair()
println("model=${chair.model()}")
}

ou

fun main() {
val chair: Chair = GamerChair()
val model = chair.model()
println("model=$model")
}

Construtores

Temos alguns modos de declararmos o(s) construtor(es) das nossas classes, começando pelo mais básico:

class MyClass(
val firstProperty: String
)

Nesse exemplo firstProperty não só é um parâmetro do nosso construtor como também um atributo da nossa classe, então podemos fazer:

fun main() {
val myClass = MyClass("custom prop")
println(myClass.firstProperty)
}

O jeito com que declaramos o construtor primário no exemplo anterior, é o modo comprimido de:

class MyClass constructor(
val firstProperty: String
)

e esse é o que precisamos fazer quando, por algum motivo precisamos colocar anotações no construtor, como por exemplo quando usamos algum framework pra fazer a injeção de dependências, e ele pede para que o construtor esteja anotado, exemplo:

class MyClass @Inject constructor(
val firstProperty: String
)

Nos exemplos acima, vimos como criar um construtor primário que simplesmente pega os valores passados e coloca nos construtores, mas e se o objetivo fosse fazer alguma lógica com eles? ou algum tipo de validação?
Para esses casos temos o bloco init, que permite colocarmos um trecho de código que será executado na ordem onde ele aparece, por exemplo:

class MyClass(
val firstProperty: String
) {
init {
println("firstProperty=$firstProperty")
}
}

Também é possível termos vários blocos init e eles serão executados na ordem em que aparecem:

class MyClass(
val firstProperty: String
) {
init {
println("firstProperty=$firstProperty")
}

val secondProperty: String = "custom prop2"

init {
println("secondProperty=$secondProperty")
}
}

mas nunca precisei fazer isso ¯\_(ツ)_/¯

Ainda falando de construtores, até o momento só declaramos construtores primários, porém podemos também ter outros construtores, desde que eles chamem o primário, como por exemplo:

class MyClass(
val firstProperty: String
) {
constructor(prop: Int) : this(prop.toString()) {
println(prop)
}
}

e a partir daqui, o céu é o limite 😃

Data classes

Em alguns casos utilizamos as classes só para “segurar os dados”, e para deixar isso mais fácil, Kotlin traz esse conceito de data class que podemos declarar da seguinte forma:

data class User(
val name: String,
val age: Int
)

Isso vai fazer com que o compilador gere as funções “comuns” desse tipo de classe com base nesse construtor primário que declaramos, essas funções comuns são: toString, equals e hashCode, alem disso esse tipo de classe permite outras coisas legais como:

Desconstrução:

fun doSomething(user: User) {
val (name, age) = user
println("name=$name, age=$age")
}

Copia:

fun main() {
val user = User("Leonardo", 23)
val otherUser = user.copy(
name = "Leonardo Ferreira"
)
}

E nesse caso o que vamos ter é uma nova instancia do usuário com o novo nome

Parâmetros nomeados e valores padrões

Quando recebemos parâmetros seja em funções, construtores ou algum outro lugar que de pra receber parâmetros, podemos nomeá-los quando vamos chamar essa função, construtor ou outra coisa, exemplo:

fun doSomething(name: String, age: Int) {
println("name=$name, age=$age")
}

fun main() {
doSomething(
name = "Leonardo",
age = 23
)
}

ou com construtores:

fun main() {
val user = User(
name = "Leonardo",
age = 23
)
}

Também podemos declarar valores padrões para os parâmetros:

fun doSomething(f: String, s: Int = 2) {
println("f=$f, s=$s")
}

isso faz com que na hora que invocamos essa função, podemos omitir o segundo parâmetro e ele assume o valor que declamarmos como default, exemplo:

fun main() {
doSomething("first")
}

e o resultado é:

f=first, s=2

Null Safe

Estamos quase chegando no final do post e eu não falei ainda da funcionalidade que está no topo da lista das mais legais.

Kotlin nos protege dos NullPointer’s (NPE’s) de uma maneira um tanto quanto diferente, em seu próprio sistema de tipos ele limita quais tipos são nullables e quais não são, isso significa que apenas os tipos nullables podem receber nulo.

E por padrão os tipos não podem receber nulo, isso significa que o trecho de código a seguir nem chega a compilar:

fun main() {
val str: String = null
}

Para fazer com que o código compile, precisamos mudar o tipo para nullable trocando de String para String?, da seguinte forma:

fun main() {
val str: String? = null
}

E isso é ótimo, pois o próprio compilador nos ajuda a lembrar de tratar nulos, por exemplo, se fizermos:

fun main() {
val str: String? = null
println(str.toUpperCase())
}

esse trecho de código também não compila, pois estamos fazendo uma chamada que pode, e nesse caso vai, causar uma NPE.

No lugar de fazermos essa chamada direta, temos que garantir que essa variável não seja nula e para isso temos algumas opções:

Smart Cast:

fun main() {
val str: String? = null
if (str != null) {
println(str.toUpperCase())
}
}

dessa maneira o compilador começa a entender que dentro do condicional if, a variável str já não é mais String?, ele se torna uma String e podemos escrever o nosso código normalmente, porém salvo de NPE’s

Safe Call:

fun main() {
val str: String? = null
println(str?.toUpperCase())
}

Esse operador também tornará nosso código seguro de NPE’s, porém fará com que a saída seja diferente, no lugar de não “printar” nada, o código exibirá “null” no console.

Elvis Operator:

Não serve exatamente pra resolver o nosso problema, mas é muito útil quando lidamos com nulo.
A ideia é quase de um ternário, porém com o objetivo de que se a parte da esquerda for null ele utilizará a da direita (juro que fica mais fácil vendo o código):

fun main() {
val str: String? = null
val myStrNonNull: String = str ?: "outra str"
println(myStrNonNull)
}

Também pode ser útil para validações, podemos fazer:

fun main() {
val str: String? = null
str ?: throw IllegalArgumentException()

println(str.toUpperCase())
}

E já que falamos de ternário vale dizer que Kotlin não tem ternário igual C ou Java. 😱
Quando precisamos fazer uma atribuição condicional, podemos fazer da seguinte forma:

fun greeting(language: String) {
val str = if (language == "pt-BR")
"Olá Mundo"
else
"Hello world"

println(str)
}

Extensões

E por último, a funcionalidade que considero a mais legal do Kotlin.

As extensões permite que adicionemos comportamento a uma classe mesmo sem ter acesso ao código ela, como por exemplo:

fun String.print() {
println(this)
}

fun main() {
"hello world".print()
}

Com isso adicionamos um comportamento dentro da classe String e fica quase que 100% transparente pra quem desenvolve, quase porque temos que importar a extension se ela estiver em um pacote diferente.

Parece muito legal, e da vontade de usar em tudo, mas vale ressaltar alguns pontos:

  1. As extensões não substituem comportamentos das classes, isso significa que o resultado do trecho a seguir é “do Something”:
class MyClass {
fun doSomething() {
println("do Something")
}
}

fun MyClass.doSomething() {
println("don't do something")
}

fun main() {
val myClass = MyClass()
myClass.doSomething()
}

2. As extensões não podem acessar atributos privados das classes, isso significa que, o código a seguir nem chega a compilar:

class MyClass(
private val message: String
)

fun MyClass.doSomethingWithMessage() {
println(this.message.toUpperCase())
}

3. Extensões são resolvidas de forma estática, então o que temos no final das contas é aquele velho e bom StringUtils que todo mundo ama só que de um jeito mais bonito:

fun String.print() {
println(this)
}

fun main() {
"hello world".print()
}

vira algo +- assim:

fun print(s: String) {
println(s)
}

fun main() {
print("hello world")
}

Bom, isso não é tudo, mas acredito ser o suficiente pra uma introdução a linguagem.
Para os que quiserem seguir aprendendo mais sobre a linguagem considero e recomendo a documentação como uma boa fonte que pode ser encontrada aqui: https://kotlinlang.org/docs/reference

E é isso, até a próxima 😃

Fontes

--

--