Classes e instâncias de objeto em Kotlin

Nos codelabs deste caminho de aprendizado, você criará o Dice Roller, um app Android. Quando o usuário "jogar o dado", um resultado aleatório será gerado. O resultado considera o número de lados do dado. Por exemplo, somente valores de 1 a 6 podem ser tirados em um dado de seis lados.

O app final ficará assim:

Para que você possa se concentrar nos novos conceitos de programação do app, você usará a ferramenta de programação em Kotlin no navegador para criar a funcionalidade principal do app. O programa enviará os resultados ao console. Depois, você implementará a interface do usuário no Android Studio.

Neste primeiro codelab, você criará um programa em Kotlin que simula um jogo de dado e gera um número aleatório, assim como um dado faria.

Prerequisites

  • Saber abrir, editar e executar código em https://try.kotlinlang.org/ (link em inglês).
  • Criar e executar um programa em Kotlin que usa variáveis e funções e exibe um resultado no console.
  • Formatar números em um texto usando um modelo de string com a notação ${variable}.

O que você aprenderá

  • Como gerar números aleatórios de forma programática para simular jogos de dado
  • Como estruturar o código criando uma classe Dice com uma variável e um método.
  • Como criar uma instância de objeto de uma classe, modificar as variáveis e chamar os métodos.

O que você criará

  • Um programa Kotlin na ferramenta de programação em Kotlin no navegador que pode jogar dados de forma aleatória.

O que é necessário

  • Um computador com conexão de Internet

Os jogos geralmente contêm um elemento aleatório. Você pode ganhar um prêmio aleatório ou avançar por um número aleatório de casas em um jogo de tabuleiro. No dia a dia, você pode usar números e letras aleatórios para definir senhas mais seguras.

Em vez de realmente jogar os dados, é possível criar um programa que simule isso para você. Cada vez que o dado é jogado, o resultado pode ser qualquer número do intervalo de valores possíveis. Felizmente, você não precisará criar seu próprio gerador de números aleatórios para esse programa. A maioria das linguagens de programação, incluindo Kotlin, tem uma forma integrada de gerar números aleatórios. Nesta tarefa, você usará o código em Kotlin para gerar um número aleatório.

Configurar o código inicial

  1. No navegador, abra o site https://try.kotlinlang.org/.
  2. Exclua todos os códigos no editor e substitua pelo código abaixo. Essa é a função main() com que você trabalhou em codelabs anteriores. Consulte Criar seu primeiro programa em Kotlin.
fun main() {

}

Usar a função aleatória

Para jogar um dado, você precisa de uma maneira de representar todos os valores válidos dele. Para um dado comum de seis lados, os resultados aceitáveis são: 1, 2, 3, 4, 5 e 6.

Anteriormente, você aprendeu que existem diferentes tipos de dados, como Int para números inteiros e String para textos. IntRange é outro tipo de dados e representa um intervalo de números inteiros, de um ponto de partida até um ponto final. IntRange é o tipo de dados indicado para representar os valores possíveis de um jogo de dado.

  1. Dentro da função main(), defina uma variável como val, chamada diceRange. Atribua essa variável a um IntRange de 1 a 6, que representa o intervalo de números inteiros que podem ser gerados por um dado de seis lados.
val diceRange = 1..6

É possível dizer que 1..6 é um intervalo em Kotlin porque ele tem um número inicial, dois pontos e um número final (sem espaços no meio). Outros exemplos de intervalos inteiros são 2..5, para os números de 2 a 5, e 100..200, para os números de 100 a 200.

Da mesma forma que chamar println() instrui o sistema a exibir o texto fornecido, é possível usar uma função chamada random() para gerar e retornar um número aleatório para determinado intervalo. Assim como antes, é possível armazenar o resultado em uma variável.

  1. Em main(), defina uma variável como val chamada randomNumber.
  2. Faça com que randomNumber tenha o valor do resultado da chamada para random() no intervalo diceRange, conforme mostrado abaixo.
 val randomNumber = diceRange.random()

Você chama random() em diceRange usando um ponto entre a variável e a chamada de função. Isso pode ser lido como "gerar um número aleatório a partir de diceRange". O resultado é armazenado na variável randomNumber.

  1. Para ver o número gerado aleatoriamente, use a notação de formatação de string (também chamada de "modelo de string") ${randomNumber} para exibi-lo, conforme mostrado abaixo.
println("Random number: ${randomNumber}")

O código finalizado ficará assim:

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. Execute o código várias vezes. Você verá o resultado conforme o exemplo abaixo, cada vez com um número aleatório diferente.
Random number: 4

Ao jogar dados, você tem objetos reais nas suas mãos. Embora o código que você acabou de criar funcione perfeitamente, é difícil imaginar que ele realmente representa um dado. Organizar um programa para que ele seja mais parecido com o que ele representa facilita a compreensão. Seria legal ter um dado programático que pudesse ser jogado.

Todos os dados funcionam basicamente da mesma forma. Eles têm as mesmas propriedades, como os lados, e o mesmo comportamento, já que precisam ser jogados. Em Kotlin, é possível criar um modelo programático que informa que o dado tem lados e pode gerar um número aleatório como resultado. Esse modelo é chamado de classe.

Com essa classe, é possível criar objetos de dados reais, chamados instâncias de objetos. Por exemplo, você pode criar um dado de 12 ou 4 lados.

Definir uma classe Dice

Nas etapas a seguir, você definirá uma nova classe chamada Dice para representar um dado.

  1. Para recomeçar, limpe o código na função main(), para ficar com o código mostrado abaixo.
fun main() {

}
  1. Abaixo dessa função main(), adicione uma linha em branco e acrescente um código para criar a classe Dice. Conforme mostrado abaixo, comece com a palavra-chave class, seguida do nome da classe e de chaves. Deixe um espaço entre as chaves para colocar o código referente à classe.
class Dice {

}

Dentro da definição de classe, é possível especificar uma ou mais propriedades para a classe usando variáveis. Os dados reais podem ter diferentes lados, cores ou pesos. Nesta tarefa, você se concentrará na propriedade do número de lados do dado.

  1. Dentro da classe Dice, adicione uma var chamada sides, referente ao número de lados que o dado terá. Defina sides como 6.
class Dice {
    var sides = 6
}

É isso. Agora você tem uma classe muito simples, representando um dado.

Criar uma instância da classe Dice

Com a classe Dice, você tem um modelo de dado. Para ter um dado real no programa, é necessário criar uma instância de objeto Dice. Caso você precisasse ter três dados, seriam criadas três instâncias de objeto.

  1. Para criar uma instância de objeto Dice, na função main(), crie uma val chamada myFirstDice e inicialize-a como uma instância da classe Dice. Há parênteses após o nome da classe, o que indica que você está criando uma nova instância de objeto a partir da classe.
fun main() {
    val myFirstDice = Dice()
}

Agora que você tem um objeto myFirstDice, criado a partir do modelo, é possível acessar as propriedades dele. A única propriedade de Dice é sides. Para acessar uma propriedade, use a "notação de ponto". Portanto, para acessar a propriedade sides de myFirstDice, chame myFirstDice.sides, digitando "myFirstDice ponto sides".

  1. Abaixo da declaração myFirstDice, adicione uma instrução println() para gerar o número de sides de myFirstDice.
println(myFirstDice.sides)

O código ficará assim:

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. Execute o programa. Ele gerará o número de sides definido na classe Dice.
6

Agora você tem uma classe Dice e um dado myFirstDice com seis sides.

Chegou a hora de jogar o dado.

Jogar o dado

Anteriormente, você usou uma função para executar a ação de exibir camadas de bolos. Jogar o dado também é uma ação que pode ser implementada como uma função. Já que todos os dados podem ser jogados, é possível adicionar uma função dentro da classe Dice. Uma função definida dentro de uma classe também é chamada de método.

  1. Na classe Dice, abaixo da variável sides, insira uma linha em branco e crie uma nova função para jogar o dado. Comece com a palavra-chave em Kotlin fun, seguida do nome do método, seguido de parênteses (), seguidos de chaves {}. Você pode deixar uma linha em branco entre as chaves para haver espaço para inserir mais códigos, conforme mostrado abaixo. A classe ficará assim:
class Dice {
    var sides = 6

    fun roll() {

    }
}

Ao jogar um dado de seis lados, ele gera um número aleatório entre 1 e 6.

  1. No método roll(), crie uma val randomNumber. Atribua um número aleatório no intervalo de 1..6. Use a notação de ponto para chamar random() no intervalo.
val randomNumber = (1..6).random()
  1. Depois de gerar o número aleatório, exiba-o no console. O método roll() final ficará como o código abaixo.
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. Para jogar myFirstDice, em main(), chame o método roll() em myFirstDice. Para chamar um método, use a "notação de ponto". Dessa forma, para chamar o método roll() de myFirstDice, insira myFirstDice.roll(), digitando "myFirstDice ponto roll()".
myFirstDice.roll()

O código final ficará assim:

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
    myFirstDice.roll()
}

class Dice {
    var sides = 6

    fun roll() {
        val randomNumber = (1..6).random()
        println(randomNumber)
    }
}
  1. Execute o código. Você verá o resultado de uma jogada aleatória abaixo do número de lados. Execute o código várias vezes. Observe que o número de lados permanece igual, mas o valor do resultado muda.
6
4

Parabéns! Você definiu uma classe Dice com uma variável sides e uma função roll(). Na função main(), você criou uma nova instância de objeto Dice e chamou o método roll() para gerar um número aleatório.

Por enquanto, você está exibindo o valor de randomNumber na função roll(), e isso funciona muito bem. Mas, às vezes, é mais útil retornar o resultado de uma função para o que a tiver chamado. Por exemplo, é possível atribuir o resultado do método roll() a uma variável e, em seguida, mover o jogador de acordo com o valor. Vejamos como isso é feito.

  1. Em main(), modifique a linha myFirstDice.roll(). Crie um val chamado diceRoll. Defina um valor igual ao retornado pelo método roll().
val diceRoll = myFirstDice.roll()

Ele ainda não faz nada, porque roll() ainda não retorna nada. Para que esse código funcione como deveria, roll() precisa retornar algo.

Nos codelabs anteriores, você aprendeu que é necessário especificar um tipo de dados para inserir argumentos nas funções. Da mesma forma, é necessário especificar um tipo de dados para o que é retornado de uma função.

  1. Mude a função roll() para especificar o tipo de dados que será retornado. Neste caso, o número aleatório é um Int. Portanto, o tipo de retorno é Int. A sintaxe para especificar o tipo de retorno é: depois do nome da função, depois dos parênteses, adicione dois pontos, espaço e a palavra-chave Int referente ao tipo de retorno da função. A definição da função ficará como o código abaixo.
fun roll(): Int {
  1. Execute o código. Você verá um erro em Problems View. Ele informa:
A ‘return'  expression is required in a function with a block body. 

Você alterou a definição da função para retornar um Int, mas o sistema informa que

o código não está retornando um Int. "Corpo do bloco" ou "corpo da função" refere-se ao código entre as chaves de uma função. Para corrigir esse erro, retorne o valor de uma função usando uma instrução return no final do corpo da função.

  1. Em roll(), remova a instrução println() e substitua por uma instrução return para randomNumber. A função roll() ficará como o exemplo abaixo.
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. Em main(), remova a instrução de exibição referente aos lados do dado.
  2. Adicione uma instrução para imprimir o valor de sides e diceRoll em uma frase informativa. A função main() final precisa ficar como o código abaixo.
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. Execute o código. O resultado ficará assim:
Your 6 sided dice rolled 4!

Veja todo o código até agora.

fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}


class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..6).random()
        return randomNumber
    }
}

Nem todos os dados têm seis lados. Existem diversos formatos e tamanhos de dados: 4 lados, 8 lados, até 120 lados.

  1. Na classe Dice, no método roll(), mude o 1..6 codificado para usar sides, de modo que o intervalo e, assim, o número aleatório, esteja sempre de acordo com o número de lados.
val randomNumber = (1..sides).random()
  1. Na função main(), abaixo e depois de exibir o resultado do dado, mude sides de FirstDice para ser definido como 20.
myFirstDice.sides = 20
  1. Copie e cole a instrução de exibição abaixo no local em que você mudou o número de lados.
  2. Substitua a exibição de diceRoll pela exibição do resultado da chamada do método roll() em myFirstDice.
println("Your ${myFirstDice.sides} sided dice has rolled a ${myFirstDice.roll()}!")

O programa ficará assim.

fun main() {
   
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")

    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..sides).random()
        return randomNumber
    }
}
  1. Execute o programa. Você verá uma mensagem para o dado de seis lados e outra para o dado de 20 lados.
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

A ideia de uma classe é representar um elemento, muitas vezes algo físico no mundo real. Neste caso, uma classe Dice representa um dado. No mundo real, o número de lados de um dado não muda. Se você quiser outro número de lados, precisará de um dado diferente. Em programação, isso significa que, em vez de mudar a propriedade de lados de uma instância de objeto Dice, você criará uma nova instância de objeto de dado, com o número de lados necessários.

Nesta tarefa, você modificará a classe Dice para especificar o número de lados ao criar uma nova instância. Mude a definição da classe Dice para aceitar um argumento para o número de lados. Isso é parecido com a forma como uma função pode aceitar argumentos como resultado.

  1. Modifique a definição da classe Dice para aceitar um argumento inteiro com o nome numSides. O código dentro da classe não mudará.
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Dentro da classe Dice, exclua a variável sides, já que agora é possível usar numSides.
  2. Além disso, corrija o intervalo para usar numSides.

A classe Dice ficará assim:

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

Ao executar esse código você verá muitos erros, já que é necessário atualizar main() para trabalhar com as mudanças na classe Dice.

  1. Em main(), para criar myFirstDice com seis lados, é necessário transmitir o número de lados como um argumento para a classe Dice, conforme mostrado abaixo.
    val myFirstDice = Dice(6)
  1. Na instrução de exibição, mude sides para numSides.
  2. Abaixo disso, exclua o código que muda sides para 20, já que essa variável não existe mais.
  3. Exclua também a instrução println abaixo dela.

A função main() ficará parecida com o código abaixo e, ao executá-la, não haverá erros.

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. Depois de exibir o primeiro dado, adicione código para criar e exibir um segundo objeto Dice, chamado mySecondDice, com 20 lados.
    val mySecondDice = Dice(20)
  1. Adicione uma instrução que jogue o dado e exiba o valor retornado.
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. A função main() concluída ficará parecida com esta:
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
    
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}
  1. Execute o programa finalizado. O resultado ficará assim:
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

Ao programar códigos, o melhor é ser conciso. Você pode eliminar a variável randomNumber e retornar o número aleatório diretamente.

  1. Mude a instrução return para retornar o número aleatório diretamente.
    fun roll(): Int {
        return (1..numSides).random()
    }

Na segunda instrução de exibição, insira a chamada para receber o número aleatório no modelo de string. É possível eliminar a variável diceRoll fazendo o mesmo na primeira instrução de exibição.

  1. Chame myFirstDice.roll() no modelo de string e exclua a variável diceRoll. As duas primeiras linhas do código main() ficarão assim.
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. Execute o código. Você verá que não há diferença no resultado.

Este é o código final após a refatoração.

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
    
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
    
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
  • Chame a função random() em um IntRange para gerar um número aleatório: (1..6).random()
  • As classes são como o modelo de um objeto. Elas podem ter propriedades e comportamentos, implementados como variáveis e funções.
  • A instância de uma classe representa um objeto, geralmente um objeto físico, como um dado. É possível chamar as ações no objeto e mudar os atributos.
  • É possível passar entradas para uma classe ao criar uma instância ao especificar um argumento para a definição da classe. Por exemplo: class Dice(val numSides: Int) e, então, criar uma instância com Dice(6).
  • As funções podem retornar algo. Especifique o tipo de dados a ser retornado na definição da função e use uma instrução return no corpo da função para retornar algo. Por exemplo: fun example(): Int { return 5 }

Faça o seguinte:

  • Forneça outro atributo de cor à classe Dice e crie várias instâncias de dados, com diferentes números de lados e cores.
  • Crie uma classe Coin, conceda a capacidade de virar, crie uma instância de classe e jogue moedas. Como você usaria a função aleatória() com um intervalo para conseguir jogar uma moeda?