0

Kotlin

A
Alfredo Neto

Kotlin a Partir do Princípio: Pacotes e Funções Básicas

 31 Aug 2017Difficulty:BeginnerLength:LongLanguages:English

Deutsch

Español

Bahasa Indonesia

Italiano

Português

中文(简体)

中文(繁體)

KotlinJavaAndroidMobile DevelopmentLanguage FundamentalsProgramming FundamentalsFunctional Programming

This post is part of a series called Kotlin From Scratch.

Kotlin From Scratch: Ranges and Collections

Kotlin From Scratch: More Fun With Functions

Portuguese (Português) translation by Luis Drum (you can also view the original English article)

Kotlin é uma linguagem moderna de programação que é compilada em bytecode Java. É gratuita e de código aberto e promete fazer a codificação para o Android ainda mais divertida.

No artigo anterior, você aprendeu sobre sequências e coleções em Kotlin. Neste tutorial, vamos continuar a aprender a linguagem, observando como organizar o código usando pacotes e depois passar para uma introdução às funções em Kotlin.

1. Pacotes

Se você estiver familiarizado com Java, você sabe que no Java são usados pacotes para agrupar classes relacionadas; por exemplo, o pacote java.util possui um número de classes utilitárias. Pacotes são declarados com a palavra-chave package, e qualquer arquivo de Kotlin com uma declaração de package no início pode conter declarações de funções, classes ou interfaces.

Declaração

Olhando para o código abaixo, declaramos um pacote com.chikekotlin.projectx usando a palavra-chave  package. Além disso, nós declaramos uma classe MyClass (discutiremos classes em Kotlin em posts futuros) dentro deste pacote.

1

2

3

package com.chikekotlin.projectx

 

class MyClass

Agora, o nome totalmente qualificado para a classe MyClass é com.chikekotlin.projectx.MyClass.

1

2

3

4

5

package com.chikekotlin.projectx

 

fun saySomething(): String {

    return "How far?"

}

No código acima, criamos uma função de nível superior (vamos chegar a isso em breve). Então, da mesma forma que para MyClass, o nome totalmente qualificado para a função saySomething() é com.chikekotlin.projectx.saySomething.

Importações

Em Kotlin, usamos a declaração import para permitir que o compilador localize as classes, funções, interfaces ou objetos a serem importados. Em Java, por outro lado, nós não podemos importar diretamente funções ou métodos — apenas classes ou interfaces.

Nós usamos import para acessar uma função, interface, classe ou objeto fora do pacote onde foi declarado.

1

2

3

4

5

import com.chikekotlin.projectx.saySomething

 

fun main(args: Array<String>) {

    saySomething() // will print "How far?"

}

No trecho de código acima, nós importamos a função saySomething() de um pacote diferente, e em seguida executamos essa função.

Kotlin também suporta importações curinga usando o operador *. Isto irá importar todas as classes, interfaces e funções declaradas no pacote ao mesmo tempo. Isso não é recomendado, porém — geralmente é melhor explicitar suas importações.

1

import com.chikekotlin.projectx.*


Importação de Codinome

Quando você tem bibliotecas que têm classes ou nomes de função conflitantes (por exemplo, cada uma delas declarar uma função com o mesmo nome), você pode usar a palavra-chave as para dar àquela entidade importada um nome temporário.

1

2

3

4

5

6

import com.chikekotlin.projectx.saySomething

import com.chikekotlin.projecty.saySomething as projectYSaySomething

 

fun main(args: Array<String>) {

    projectYSaySomething()

}

Observe que o nome temporário é usado somente dentro do arquivo onde ele foi atribuído.


Advertisement

2. Funções

Uma função agrupa uma série de instruções de código que executam uma tarefa.Os detalhes da implementação da função estão ocultos do chamador.

Em Kotlin, funções são definidas usando a palavra-chave fun, como mostrado no exemplo a seguir:

1

2

3

4

5

fun hello(name: String): String {

    return "Hello $name"

}

val message = hello("Chike")

print(message) // will print "Hello Chike"

No código acima, definimos uma função simples hello() com um único parâmetro name do tipo String. Essa função retorna um tipo String. O formato de definição de parâmetros para funções é name: type, por exemplo, age: Intprice: Doublestudent: StudentClass.

1

2

3

4

5

fun hello(name: String): Unit {

   print("Hello $name")

}

 

hello("Chike") // will print "Hello Chike"

A função acima é semelhante a anterior, mas observe que esta tem um tipo de retorno Unit. Porque esta função não retorna nenhum valor significativo para nós — ela só imprime uma mensagem — seu tipo de retorno é Unit por padrão. Unit é um objeto de Kotlin (discutiremos objetos de Kotlin em posts posteriores) que é similar aos tipos Void em Java e C.

1

2

3

public object Unit {

    override fun toString() = "kotlin.Unit"

}

Note que se você não declarar explicitamente o tipo de retorno como Unit, o tipo é inferido pelo compilador.

1

2

3

fun hello(name: String) { // will still compile

   print("Hello $name")

}

Funções de Linha Única

Funções de linha única ou de uma linha são funções que são apenas simples expressões. Nessa função, podemos livrar-nos das chaves e usar o símbolo = antes da expressão. Em outras palavras, nos livramos do bloco de função.

1

2

3

fun calCircumference(radius: Double): Double {

    return (2 * Math.PI) * radius

}

A função acima pode ser encurtada em uma única linha:

1

fun calCircumference(radius: Double) = (2 * Math.PI) * radius

Olhando para a função atualizada acima, você pode ver que nós fizemos nosso código mais conciso, removendo as chaves {}, a palavra-chave return e também o tipo de retorno (que é inferido pelo compilador).

Você ainda pode incluir o tipo de retorno para ser mais explícito, se você quiser.

1

fun calCircumference(radius: Double): Double = (2 * Math.PI) * radius


Parâmetros Nomeados

Parâmetros nomeados permitem funções mais legíveis, nomeando os parâmetros que estão sendo passados para uma função quando chamada.

No exemplo a seguir, criamos uma função que imprime meu nome completo.

1

2

3

fun sayMyFullName(firstName: String, lastName: String, middleName: String): Unit {

    print("My full name is $firstName $middleName $lastName");

}

Para executar a função acima, nós apenas a chamaríamos assim:

1

sayMyFullName("Chike", "Nnamdi", "Mgbemena")

Olhando para a chamada de função acima, nós não sabemos quais argumentos de tipo String equivalem a quais parâmetros da função (embora alguns IDEs como o IntelliJ IDEA possam nos ajudar). Os usuários da função terão que olhar para a assinatura da função (ou o código-fonte) ou a documentação para saber a qual cada parâmetro corresponde.

1

sayMyFullName(firstName = "Chike", middleName = "Nnamdi", lastName = "Mgbemena")

Na segunda chamada de função acima, nós fornecemos os nomes dos parâmetros antes dos valores dos argumentos. Você pode ver que essa chamada de função é mais clara e mais legível do que a anterior. Esta maneira de chamar funções ajuda a reduzir a possibilidade de bugs que podem acontecer quando os argumentos do mesmo tipo são trocados por engano.

O chamador também pode alterar a ordem dos parâmetros usando parâmetros nomeada. Por exemplo:

1

sayMyFullName(lastName = "Mgbemena", middleName = "Nnamdi", firstName = "Chike") // will still compile

No código acima, nós trocamos a posição do argumento firstName com lastName. A ordem dos argumentos com parâmetros nomeados não importa, porque o compilador irá mapear cada um para o parâmetro de função correto.


Advertisement

Parâmetros Padrão

Em Kotlin, poderemos dar a uma função valores padrão para qualquer um dos seus parâmetros. Esses valores padrão serão usados se nada é atribuído aos argumentos durante a chamada de função. Para fazer isso em Java, teríamos de criar diferentes métodos sobrecarregados.

Aqui, em nosso método calCircumference(), nós modificamos o método adicionando um valor padrão para o parâmetro pi —Math.PI, uma constante do pacote java.lang.Math.

1

fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius

Quando nós chamamos esta função, podemos passar nosso valor aproximado de pi ou usar o padrão.

1

2

print(calCircumference(24.0)) // used default value for PI and prints 150.79644737231007

print(calCircumference(24.0, 3.14)) // passed value for PI and prints 150.72

Vamos ver outro exemplo.

1

2

3

fun printName(firstName: String, middleName: String = "N/A", lastName: String) {

    println("first name: $firstName - middle name: $middleName - last name: $lastName")

}

No código a seguir, nós tentamos chamar a função, mas ele não vai compilar:

1

printName("Chike", "Mgbemena") // won't compile

Na chamada de função acima, estou passando meu primeiro nome e sobrenome para a função e esperando usar o valor padrão para o nome do meio. Mas isso não vai compilar porque o compilador está confuso. Não sabe para que serve o argumento "Mgbemena" — é para o middleName ou para o parâmetro lastName?

Para resolver esse problema, podemos combinar parâmetros nomeados e parâmetros padrão.

1

printName("Chike", lastName = "Mgbemena") // will now compile


Interoperabilidade Java

Dado que o Java não suporta valores de parâmetro padrão em métodos, você precisará especificar explicitamente todos os valores de parâmetro, quando você chamar uma função de Kotlin a partir do Java. Mas o Kotlin nos fornece uma funcionalidade para facililtar para os chamadores de Java, anotando a função de Kotlin com @JvmOverloads. Esta anotação irá instruir o compilador de Kotlin para gerar as funções Java sobrecarregadas para nós.

No exemplo a seguir, nós anotamos a função calCirumference() com @JvmOverloads.

1

2

@JvmOverloads

fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius

O código a seguir foi gerado pelo compilador Kotlin para que os chamadores Java possam então escolher qual chamar.

1

2

3

// Java

double calCircumference(double radius, double pi);

double calCircumference(double radius);

Na última definição de método Java gerada, o parâmetro de pi foi omitido. Isto significa que o método usará o valor de pi padrão.

Argumentos Ilimitados

Em Java, podemos criar um método para receber um número indeterminado de argumentos, incluindo reticências (...) depois de um tipo na lista de parâmetros do método. Este conceito também é suportado por funções Kotlin com o uso do modificador vararg seguido do nome do parâmetro.

1

2

3

4

5

6

fun printInts(vararg ints: Int): Unit {

    for (n in ints) {

        print("$n\t")

    }

}

printInts(1, 2, 3, 4, 5, 6) // will print 1    2  3 4   5   6

O modificador vararg permite que os chamadores passem os argumentos em uma lista separada por vírgulas. Nos bastidores, esta lista de argumentos irá ser empacotada em uma matriz.

Quando uma função tem vários parâmetros, o parâmetro vararg é normalmente o último. Também é possível ter parâmetros após o vararg, mas você precisará usar parâmetros nomeados para especificá-los quando você chamar a função.

1

2

3

4

5

6

7

8

fun printNumbers(myDouble: Double, myFloat: Float, vararg ints: Int) {

    println(myDouble)

    println(myFloat)

    for (n in ints) {

        print("$n\t")

    }

}

printNumbers(1.34, 4.4F, 2, 3, 4, 5, 6) // will compile

Por exemplo, no código acima, o parâmetro com o modificador vararg está na última posição em uma lista de múltiplos parâmetros (isto é o que geralmente fazemos). Mas e se não o queremos na última posição? No exemplo a seguir, ele está na segunda posição.

01

02

03

04

05

06

07

08

09

10

11

fun printNumbers(myDouble: Double, vararg ints: Int, myFloat: Float) {

    println(myDouble)

    println(myFloat)

    for (n in ints) {

        print("$n\t")

    }

}

printNumbers(1.34, 2, 3, 4, 5, 6, myFloat = 4.4F) // will compile

printNumbers(1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will not compile

printNumbers(myDouble = 1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will also not

compile

Como se pode observar no código atualizado acima, usamos argumentos nomeados no último parâmetro para resolver isto.

Operador de Propagação

Digamos que queremos passar uma matriz de inteiros para nossa função printNumbers(). A função espera que os valores sejam expostos em uma lista de parâmetros, no entanto. Se você tentar passar a matriz diretamente para printNumbers(), você verá que ela não vai compilar.

1

2

val intsArray: IntArray = intArrayOf(1, 3, 4, 5)

printNumbers(1.34, intsArray, myFloat = 4.4F) // won't compile

Para resolver este problema, precisamos usar o operador de propagação *. Este operador irá descompactar a matriz e em seguida, passar os elementos individuais como argumentos para a função, para nós.

1

2

val intsArray: IntArray = intArrayOf(1, 3, 4, 5)

printNumbers(1.34, *intsArray, myFloat = 4.4F) // will now compile

Inserindo o operador de propagação * na frente do intsArray na lista de argumentos da função, o código agora compila e produz o mesmo resultado como se passassemos os elementos de intsArray como uma lista de argumentos separada por vírgulas .

Retornar Valores Múltiplos

Às vezes nós queremos retornar vários valores de uma função. Uma maneira é usar o tipo Pair em Kotlin para criar um Pair e retorná-lo. Essa estrutura Pair inclui dois valores que podem ser acessados mais tarde. Este tipo de Kotlin pode aceitar quaisquer tipos que você fornecer ao seu construtor. E, além do mais, os dois tipos nem precisam ser o mesmo.

01

02

03

04

05

06

07

08

09

10

fun getUserNameAndState(id: Int): Pair<String?, String?> {

    require(id > 0, { "Error: id is less than 0" })

 

    val userNames: Map<Int, String> = mapOf(101 to "Chike", 102 to "Segun", 104 to "Jane")

    val userStates: Map<Int, String> = mapOf(101 to "Lagos", 102 to "Imo", 104 to "Enugu")

 

    val userName = userNames[id]

    val userState = userStates[id]

    return Pair(userName, userState)

}

Na função acima, construímos um novo Pair, passando as variáveis userName e userState como o primeiro e segundo argumento respectivamente para seu construtor e então retornamos este Pair para o chamador.

Outra coisa a observar é que usamos uma função chamada require() na função getUserNameAndState(). Esta função auxiliar da biblioteca padrão é usada para dar aos chamadores de função uma pré-condição para atender, senão será lançada uma IllegalArgumentException (discutiremos exceções em Kotlin em um post futuro). O segundo argumento opcional para require() é uma função literal de retornar uma mensagem a ser exibida se a exceção é lançada. Por exemplo, chamando a função getUserNameAndState() e passando -1 como um argumento irá disparar:

IntelliJ IDEA code execution result

Recuperar Dados de Pair

1

2

3

val userNameAndStatePair: Pair<String?, String?> = getUserNameAndState(101)

println(userNameAndStatePair.first) // Chike

println(userNameAndStatePair.second) // Lagos

No código acima, acessamos o primeiro e o segundo valor do tipo Pair usando suas propriedades first e second.

No entanto, há uma maneira melhor de fazer isso: desestruturação.

1

2

3

val (name, state) = getUserNameAndState(101)

println(name) // Chike

println(state) // Lagos

O que fizemos no código atualizado acima é atribuir diretamente o primeiro e segundo valores do tipo Pair retornados para as variáveis name e state respectivamente. Esse recurso é chamado de declaração de desestruturação.

Valores de Retorno Triplo e Além

Agora, se você quiser retornar três valores de uma vez? Kotlin nos fornece outro tipo útil chamado Triple.

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

fun getUserNameStateAndAge(id: Int): Triple<String?, String?, Int> {

    require(id > 0, { "id is less than 0" })

     

    val userNames: Map<Int, String> = mapOf(101 to "Chike", 102 to "Segun", 104 to "Jane")

    val userStates: Map<Int, String> = mapOf(101 to "Lagos", 102 to "Imo", 104 to "Enugu")

     

    val userName = userNames[id]

    val userState = userStates[id]

    val userAge = 6

    return Triple(userNames[id], userStates[id], userAge)

}

 

val (name, state, age) = getUserNameStateAndAge(101)

println(name) // Chike

println(state) // Lagos

println(age) // 6

Tenho certeza que alguns de vocês estão se perguntando o que fazer se você quiser mais do que três valores de retorno. A resposta para isso estará em um próximo post, quando discutirmos as classes de dados de Kotlin.

Conclusão


0
0

Comentários (0)

alfredo gelk neto

Brasil