Introdução à linguagem Kotlin

Introdução à linguagem Kotlin

*1. Visão geral *

Neste tutorial, veremos o Kotlin, um novo idioma no mundo da JVM e alguns de seus recursos básicos, incluindo classes, herança, declarações condicionais e construções em loop.

Em seguida, examinaremos alguns dos principais recursos que tornam o Kotlin uma linguagem atraente, incluindo segurança nula, classes de dados, funções de extensão e modelos String.

===* 2. Dependências do Maven *

Para usar o Kotlin no seu projeto Maven, você precisa adicionar a biblioteca padrão do Kotlin ao seu pom.xml:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>1.0.4</version>
</dependency>

Para adicionar suporte JUnit ao Kotlin, você também precisará incluir as seguintes dependências:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-test-junit</artifactId>
    <version>1.0.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Por fim, você precisará configurar os diretórios de origem e o plug-in Kotlin para executar uma compilação do Maven:

<build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
    <plugins>
        <plugin>
            <artifactId>kotlin-maven-plugin</artifactId>
            <groupId>org.jetbrains.kotlin</groupId>
            <version>1.0.4</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Você pode encontrar a versão mais recente de https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jetbrains.kotlin%22%20AND%20a%3A%22kotlin-maven-plugin% 22 [kotlin-maven-plugin] no Maven Central.

3. Sintaxe básica

Vejamos os blocos de construção básicos do Kotlin Language.

Há alguma semelhança com Java (por exemplo, definir pacotes é da mesma maneira). Vamos dar uma olhada nas diferenças.

3.1. Definindo Funções

Vamos definir uma função com dois parâmetros Int com o tipo de retorno Int:

fun sum(a: Int, b: Int): Int {
    return a + b
}

3.2. Definindo variáveis ​​locais

Atribuir variável local local (somente leitura):

val a: Int = 1
val b = 1
val c: Int
c = 1

Observe que o tipo de uma variável b é inferido por um compilador Kotlin. Também podemos definir variáveis ​​mutáveis:

var x = 5
x += 1

4. Campos opcionais

O Kotlin possui uma sintaxe básica para definir um campo que pode ser anulável (opcional). Quando queremos declarar que o tipo de campo é nulo, precisamos usar o tipo com sufixo com um ponto de interrogação:

val email: String?

Quando você definiu um campo nulo, é perfeitamente válido atribuir um null a ele:

val email: String? = null

Isso significa que em um campo de email pode ser um nulo. Se escrevermos:

val email: String = "value"

Em seguida, precisamos atribuir um valor ao campo de email na mesma declaração que declaramos email. Não pode ter um valor nulo. Voltaremos a Kotlin null safety em uma seção posterior.

===* 5. Aulas *

Vamos demonstrar como criar uma classe simples para gerenciar uma categoria específica de um produto. Nossa classe ItemManager abaixo tem um construtor padrão que preenche dois campos - categoryId e dbConnection - e um campo opcional email:

class ItemManager(val categoryId: String, val dbConnection: String) {
    var email = ""
   //...
}

Essa construção ItemManager (…) _ cria um construtor e dois campos em nossa classe: _categoryId e dbConnection

Observe que nosso construtor usa a palavra-chave val para seus argumentos - isso significa que os campos correspondentes serão final e imutáveis. Se tivéssemos usado a palavra-chave var (como fizemos ao definir o campo email), esses campos seriam mutáveis.

Vamos criar uma instância do ItemManager usando o construtor padrão:

ItemManager("cat_id", "db://connection")

Nós poderíamos construir ItemManager usando parâmetros nomeados. É muito útil quando você gosta deste exemplo de função que aceita dois parâmetros com o mesmo tipo, por exemplo String, e você não deseja confundir uma ordem deles. Usando parâmetros de nomenclatura, você pode escrever explicitamente qual parâmetro está atribuído. Na classe ItemManager, existem dois campos, categoryId e dbConnection, para que ambos possam ser referenciados usando parâmetros nomeados:

ItemManager(categoryId = "catId", dbConnection = "db://Connection")

É muito útil quando precisamos passar mais argumentos para uma função.

Se você precisar de construtores adicionais, defina-os usando a palavra-chave constructor. Vamos definir outro construtor que também defina o campo email:

constructor(categoryId: String, dbConnection: String, email: String)
  : this(categoryId, dbConnection) {
    this.email = email
}

Observe que esse construtor chama o construtor padrão que definimos acima antes de definir o campo de email. E como já definimos categoryId e dbConnection como imutáveis ​​usando a palavra-chave val no construtor padrão, não precisamos repetir a palavra-chave val no construtor adicional.

Agora, vamos criar uma instância usando o construtor adicional:

ItemManager("cat_id", "db://connection", "[email protected]")

Se você deseja definir um método de instância em ItemManager, faça isso usando a palavra-chave fun:

fun isFromSpecificCategory(catId: String): Boolean {
    return categoryId == catId
}

===* 6. Herança *

Por padrão, as classes do Kotlin são fechadas para extensão - o equivalente a uma classe marcada final em Java.

Para especificar que uma classe está aberta para extensão, você usaria a palavra-chave open ao definir a classe.

Vamos definir uma classe Item que esteja aberta para extensão:

open class Item(val id: String, val name: String = "unknown_name") {
    open fun getIdOfItem(): String {
        return id
    }
}

Observe que também denotamos o método _getIdOfItem () _ como aberto. Isso permite que ele seja substituído.

Agora, vamos estender a classe Item e substituir o método _getIdOfItem () _:

class ItemWithCategory(id: String, name: String, val categoryId: String) : Item(id, name) {
    override fun getIdOfItem(): String {
        return id + name
    }
}

===* 7. Declarações condicionais *

No Kotlin, a instrução condicional if é equivalente a uma função que retorna algum valor. Vejamos um exemplo:

fun makeAnalyisOfCategory(catId: String): Unit {
    val result = if (catId == "100") "Yes" else "No"
    println(result)
}

Neste exemplo, vemos que se catId for igual a “100”, o bloco condicional retornará “Yes”, caso contrário, ele retornará “No” . O valor retornado será atribuído a result.

Você pode criar um bloco if _ – _ else normal:

val number = 2
if (number < 10) {
    println("number less that 10")
} else if (number > 10) {
    println("number is greater that 10")
}

O Kotlin também possui um comando when muito útil que age como uma instrução de switch avançada:

val name = "John"
when (name) {
    "John" -> println("Hi man")
    "Alice" -> println("Hi lady")
}

8. Colecções

Existem dois tipos de coleções no Kotlin: mutáveis ​​e imutáveis. Quando criamos uma coleção imutável, isso significa que é somente leitura:

val items = listOf(1, 2, 3, 4)

Não há nenhum elemento de função de adição nessa lista.

Quando queremos criar uma lista mutável que possa ser alterada, precisamos usar o método _mutableListOf () _:

val rwList = mutableListOf(1, 2, 3)
rwList.add(5)

Uma lista mutável possui o método _add () _ para que possamos acrescentar um elemento a ela. Também existem métodos equivalentes para outros tipos de coleções: _mutableMapOf (), mapOf (), setOf (), mutableSetOf () _

9. Exceções

O mecanismo de tratamento de exceções é muito semelhante ao do Java.

Todas as classes de exceção estendem Throwable. A exceção deve ter uma mensagem, rastreamento de pilha e uma causa opcional.* Todas as exceções no Kotlin estão desmarcadas *, o que significa que o compilador não nos obriga a capturá-las.

Para lançar um objeto de exceção, precisamos usar a expressão throw:

throw Exception("msg")

O tratamento da exceção é feito usando o bloco try… catch (finalmente opcional):

try {

}
catch (e: SomeException) {

}
finally {

}

10. Lambdas

No Kotlin, poderíamos definir funções lambda e passá-las como argumentos para outras funções.

Vamos ver como definir um lambda simples:

val sumLambda = { a: Int, b: Int -> a + b }

Definimos a função sumLambda que recebe dois argumentos do tipo Int como argumento e retorna Int.

Poderíamos passar um lambda por:

@Test
fun givenListOfNumber_whenDoingOperationsUsingLambda_shouldReturnProperResult() {
   //given
    val listOfNumbers = listOf(1, 2, 3)

   //when
    val sum = listOfNumbers.reduce { a, b -> a + b }

   //then
    assertEquals(6, sum)
}

*11. Construções em loop *

No Kotlin, o loop pelas coleções pode ser feito usando uma construção padrão for..in:

val numbers = arrayOf("first", "second", "third", "fourth")
for (n in numbers) {
    println(n)
}

Se queremos iterar em um intervalo de números inteiros, podemos usar uma construção de intervalo:

for (i in 2..9 step 2) {
    println(i)
}

Observe que o intervalo no exemplo acima é inclusivo nos dois lados. O parâmetro step é opcional e é equivalente a incrementar o contador duas vezes em cada iteração. A saída será a seguinte:

2
4
6
8

Poderíamos usar uma função rangeTo () _ definida na classe _Int da seguinte maneira:

1.rangeTo(10).map{ it* 2 }

O resultado conterá (observe que _rangeTo () _ também é inclusivo):

[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

*12. Segurança nula *

Vejamos um dos principais recursos do Kotlin - segurança nula, incorporada ao idioma. Para ilustrar por que isso é útil, criaremos um serviço simples que retorna um objeto Item:

class ItemService {
    fun findItemNameForId(id: String): Item? {
        val itemId = UUID.randomUUID().toString()
        return Item(itemId, "name-$itemId");
    }
}

O importante a ser observado é o tipo retornado desse método. É um objeto seguido pelo ponto de interrogação. É uma construção da linguagem Kotlin, o que significa que o Item retornado desse método pode ser nulo. Precisamos lidar com esse caso em tempo de compilação, decidindo o que queremos fazer com esse objeto (é mais ou menos equivalente ao tipo Java 8 _Optional <T> _).

Se a assinatura do método for do tipo sem ponto de interrogação:

fun findItemNameForId(id: String): Item

então, o código de chamada não precisará lidar com um caso nulo, porque é garantido pelo compilador e pela linguagem Kotlin, que o objeto retornado não pode ser nulo.

Caso contrário,* se houver um objeto anulável passado para um método e esse caso não for tratado, ele não será compilado. *

Vamos escrever um caso de teste para a segurança do tipo Kotlin:

val id = "item_id"
val itemService = ItemService()

val result = itemService.findItemNameForId(id)

assertNotNull(result?.let { it -> it.id })
assertNotNull(result!!.id)

Estamos vendo aqui que, depois de executar o método findItemNameForId (), _ o tipo retornado é Kotlin _Nullable. Para acessar um campo desse objeto (id), precisamos lidar com esse caso em tempo de compilação. O método let () _ será executado apenas se um resultado for não nulo. O campo _Id pode ser acessado dentro de uma função lambda porque é nulo seguro.

Outra maneira de acessar esse campo de objeto nulo é usar o operador Kotlin _ !! ._ É equivalente a:

if (result == null){
    throwNpe();
}
return result;

O Kotlin irá verificar se esse objeto é um null, se sim, ele lançará uma _NullPointerException, _ caso contrário, ele retornará um objeto apropriado. A função _throwNpe () _ é uma função interna do Kotlin.

===* 13. Classes de dados *

Uma construção de linguagem muito agradável que pode ser encontrada no Kotlin são as classes de dados (é equivalente à "classe de caso" da linguagem Scala). O objetivo dessas classes é apenas reter dados. No nosso exemplo, tivemos uma classe Item que apenas contém os dados:

data class Item(val id: String, val name: String)

O compilador criará para nós os métodos hashCode () _, _equals () _ e _toString () _. É uma boa prática tornar as classes de dados imutáveis, usando uma palavra-chave _val. As classes de dados podem ter valores de campo padrão:

data class Item(val id: String, val name: String = "unknown_name")

Vemos que o campo name tem um valor padrão "unknown_name" .

===* 14. Funções de extensão *

Suponha que tenhamos uma classe que faz parte da biblioteca de terceiros, mas queremos estendê-la com um método adicional. O Kotlin nos permite fazer isso usando as funções de extensão.

Vamos considerar um exemplo em que temos uma lista de elementos e queremos extrair um elemento aleatório dessa lista. Queremos adicionar uma nova função random () _ à classe _List de terceiros.

Veja como fica no Kotlin:

fun <T> List<T>.random(): T? {
    if (this.isEmpty()) return null
    return get(ThreadLocalRandom.current().nextInt(count()))
}

O mais importante a ser observado aqui é a assinatura do método. O método é prefixado com um nome da classe à qual estamos adicionando esse método extra.

Dentro do método de extensão, operamos no escopo de uma lista, portanto, usando este deu acesso a métodos de instância de lista como isEmpty () _ ou _count () . Então, podemos chamar o método _random () _ em qualquer lista que está nesse escopo:

fun <T> getRandomElementOfList(list: List<T>): T? {
    return list.random()
}

Criamos um método que pega uma lista e, em seguida, executa a função de extensão personalizada _random () _ que foi definida anteriormente. Vamos escrever um caso de teste para nossa nova função:

val elements = listOf("a", "b", "c")

val result = ListExtension().getRandomElementOfList(elements)

assertTrue(elements.contains(result))

A possibilidade de definir funções que “estendem” as classes de terceiros é um recurso muito poderoso e pode tornar nosso código mais conciso e legível.

===* 15. Modelos de string *

Um recurso muito bom da linguagem Kotlin é a possibilidade de usar modelos para Strings. É muito útil porque não precisamos concatenar Strings manualmente:

val firstName = "Tom"
val secondName = "Mary"
val concatOfNames = "$firstName + $secondName"
val sum = "four: ${2 + 2}"

Também podemos avaliar uma expressão dentro do bloco _ $ \ {} _:

val itemManager = ItemManager("cat_id", "db://connection")
val result = "function result: ${itemManager.isFromSpecificCategory("1")}"

16. Interoperabilidade Kotlin/Java

A interoperabilidade Kotlin - Java é perfeitamente fácil. Vamos supor que tenhamos uma classe Java com um método que opera em _String: _

class StringUtils{
    public static String toUpperCase(String name) {
        return name.toUpperCase();
    }
}

Agora queremos executar esse código da nossa classe Kotlin. Só precisamos importar essa classe e poderíamos executar o método java do Kotlin sem problemas:

val name = "tom"

val res = StringUtils.toUpperCase(name)

assertEquals(res, "TOM")

Como vemos, usamos o método java do código Kotlin.

Chamar o código Kotlin de um Java também é muito fácil. Vamos definir a função Kotlin simples:

class MathematicsOperations {
    fun addTwoNumbers(a: Int, b: Int): Int {
        return a + b
    }
}

A execução de _addTwoNumbers () _ a partir do código Java é muito fácil:

int res = new MathematicsOperations().addTwoNumbers(2, 4);

assertEquals(6, res);

Vemos que a chamada para o código Kotlin foi transparente para nós.

Quando definimos um método em java cujo tipo de retorno é um void, no Kotlin o valor retornado será do tipo Unit.

Existem alguns identificadores especiais na linguagem Java (is, object, in, ..) que quando usados ​​no código Kotlin precisam ser escapados. Por exemplo, poderíamos definir um método que tem um nome _object () _, mas precisamos lembrar de escapar desse nome, pois esse é um identificador especial em java:

fun `object`(): String {
    return "this is object"
}

Então poderíamos executar esse método:

`object`()

===* 17. Conclusão*

Este artigo apresenta uma introdução ao idioma Kotlin e seus principais recursos. Começa pela introdução de conceitos simples, como loops, instruções condicionais e definição de classes. Em seguida, mostra alguns recursos mais avançados, como funções de extensão e segurança nula.

A implementação de todos esses exemplos e trechos de código pode ser encontrada em o projeto GitHub - este é um projeto do Maven, portanto, deve ser fácil importar e executar como está.