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>
Você pode encontrar as versões mais recentes de https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jetbrains.kotlin%22%20AND%20a%3A%22kotlin-stdlib%22 [ kotlin-stdlib], https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jetbrains.kotlin%22%20AND%20a%3A%22kotlin-test-junit%22 [ kotlin-test-junit] e junit no Maven Central .
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á.