O livro “Head First. Kotlin »

imagem Oi, habrozhiteli! Temos um livro publicado para estudar Kotlin usando a técnica Head First, que vai além da sintaxe e das instruções para resolver problemas específicos. Este livro fornecerá tudo o que você precisa - desde o básico do idioma até os métodos avançados. E você pode praticar a programação orientada a objetos e funcional.

Um trecho "Classes de dados" é apresentado sob o corte.

Trabalhar com dados


Ninguém quer perder tempo e refazer o que já foi feito. A maioria dos aplicativos usa classes para armazenar dados. Para simplificar o trabalho, os criadores do Kotlin propuseram o conceito de uma classe de dados. Neste capítulo, você aprenderá como as classes de dados o ajudam a escrever códigos mais elegantes e concisos com os quais você só podia sonhar antes. Examinaremos as funções auxiliares das classes de dados e aprenderemos como decompor um objeto de dados em componentes. Ao mesmo tempo, descreveremos como os valores padrão dos parâmetros tornam o código mais flexível e também apresentaremos a Any, o ancestral de todas as superclasses.

O operador == chama uma função chamada igual


Como você já sabe, o operador == pode ser usado para verificar a igualdade. Cada vez que a instrução == é executada, uma função chamada igual é chamada. Cada objeto contém uma função igual, e a implementação dessa função determina o comportamento do operador ==.

Por padrão, a função equals para verificar a igualdade verifica se duas referências de variável ao mesmo objeto.

Para entender como funciona, imagine duas variáveis ​​Wolf nomeadas w1 e w2. Se w1 e w2 contêm referências a um objeto Wolf, ao compará-los com o operador ==, o resultado é verdadeiro:

imagem

Mas se w1 e w2 contêm referências a diferentes objetos Wolf, compará-los com o operador == fornece o resultado falso, mesmo que os objetos contenham os mesmos valores de propriedade.

imagem

Como mencionado anteriormente, a função igual é automaticamente incluída em todos os objetos que você cria. Mas de onde vem essa função?

é igual a herda da superclasse Qualquer


Cada objeto contém uma função chamada igual porque sua classe herda uma função de uma classe chamada Qualquer. A classe Any é o ancestral de todas as classes: a superclasse resultante de tudo. Cada classe que você define é uma subclasse de Qualquer, e você não precisa apontar isso no programa. Portanto, se você escrever um código de classe chamado myClass, que se parece com isso:

class MyClass { ... } 

O compilador o converterá automaticamente para o seguinte formato:
imagem

Cada classe é uma subclasse de Qualquer e herda seu comportamento. Cada classe é uma subclasse de Qualquer e você não precisa reportar isso no programa.

A importância de qualquer herança


Incluir Any como a superclasse resultante tem duas vantagens importantes:

  • Ele garante que toda classe herda um comportamento comum. A classe Any define um comportamento importante do qual depende a operação do sistema. E como cada classe é uma subclasse de Qualquer, esse comportamento é herdado por todos os objetos que você cria. Portanto, a classe Any define uma função chamada igual e, portanto, essa função é automaticamente herdada por todos os objetos.
  • Isso significa que o polimorfismo pode ser usado com qualquer objeto. Cada classe é uma subclasse de Qualquer, portanto, qualquer objeto que você criar terá a classe Qualquer como seu supertipo final. Isso significa que você pode criar uma função com Qualquer parâmetro ou Qualquer tipo de retorno que funcione com objetos de qualquer tipo. Isso também significa que você pode criar matrizes polimórficas para armazenar objetos de qualquer tipo com o código do seguinte formato:

 val myArray = arrayOf(Car(), Guitar(), Giraffe()) 

O compilador percebe que cada objeto na matriz possui um protótipo comum de Qualquer e, portanto, cria uma matriz do tipo Array.

O comportamento geral herdado pela classe Any merece uma análise mais detalhada.

Comportamento comum herdado de Qualquer


A classe Qualquer define várias funções herdadas por cada classe. Aqui estão exemplos de funções básicas e seu comportamento:

  • igual a (qualquer: Qualquer): Booleano
    Verifica se dois objetos são considerados "iguais". Por padrão, a função retorna true se for usada para verificar um objeto ou false - para objetos diferentes. Nos bastidores, a função igual é chamada toda vez que o operador == é usado no programa.

 val w1 = Wolf() val w1 = Wolf() val w2 = Wolf() val w2 = w1 println(w1.equals(w2)) println(w1.equals(w2)) false (equals  false, true (equals  true,   w1  w2   w1  w2        .)      —   ,   w1 == w2. 

  • hashCode (): Int
    Retorna um código de hash para um objeto. Os códigos de hash são frequentemente usados ​​por algumas estruturas de dados para armazenar e recuperar valores com eficiência.

 val w = Wolf() println(w.hashCode()) 

523429237 (Valor do código de hash w)

  • toString (): String
    Retorna uma mensagem String representando o objeto. Por padrão, a mensagem contém o nome da classe e um número, dos quais geralmente não nos importamos.

 val w = Wolf() println(w.toString()) 

Lobo @ 1f32e575

Por padrão, a função igual verifica se dois objetos são o mesmo objeto real.

A função equals determina o comportamento do operador ==.

A classe Qualquer fornece uma implementação padrão para todas as funções listadas e essas implementações são herdadas por todas as classes. No entanto, você pode substituir essas implementações para alterar o comportamento padrão de todas as funções listadas.

Verificação simples de equivalência de dois objetos


Em algumas situações, você precisa alterar a implementação da função equals para alterar o comportamento do operador ==.

Suponha que você tenha uma classe Recipe que permita criar objetos para armazenar receitas. Em tal situação, é provável que você considere dois objetos de receita iguais (ou equivalentes) se eles contiverem uma descrição da mesma receita. Digamos que a classe Recipe seja definida com duas propriedades - title e isVegetarian:

 class Recipe(val title: String, val isVegetarian: Boolean) { } 

O operador == retornará true se for usado para comparar dois objetos Recipe com as mesmas propriedades, title e isVegetarian:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) 

imagem

Embora você possa alterar o comportamento do operador == escrevendo código adicional para substituir a função equals, os desenvolvedores do Kotlin forneceram uma solução mais conveniente: eles criaram o conceito de uma classe de dados. Vamos ver o que são essas classes e como elas são criadas.

A classe de dados permite criar objetos de dados.


Uma classe de dados é uma classe para criar objetos para armazenar dados. Inclui ferramentas úteis para trabalhar com dados - por exemplo, uma nova implementação da função equals, que verifica se dois objetos de dados contêm os mesmos valores de propriedade. Se dois objetos contiverem os mesmos dados, eles poderão ser considerados iguais.

Para definir uma classe de dados, preceda a definição de dados usual com a palavra-chave data. O código a seguir converte a classe Recipe criada anteriormente em uma classe de dados:

 data class Recipe(val title: String, val isVegetarian: Boolean) { } 

O prefixo de dados converte uma classe regular em uma classe de dados.

Como criar objetos com base em uma classe de dados


Os objetos da classe de dados são criados da mesma maneira que os objetos de classe regulares: chamando o construtor dessa classe. Por exemplo, o código a seguir cria um novo objeto de dados de receita e o atribui a uma nova variável chamada r1:

 val r1 = Recipe("Chicken Bhuna", false) 

As classes de dados substituem automaticamente suas funções iguais para alterar o comportamento do operador ==, para que a igualdade de objetos seja verificada com base nos valores de propriedade de cada objeto. Se, por exemplo, você criar dois objetos Receita com os mesmos valores de propriedade, a comparação dos dois objetos com o operador == fornecerá o resultado verdadeiro, porque os mesmos dados são armazenados neles:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) //r1 == r2  true 

r1 e r2 são considerados "iguais" porque dois objetos Recipe contêm os mesmos dados.

Além da nova implementação da função equals herdada da superclasse Any, classes de dados
também substitui as funções hashCode e toString. Vamos ver como essas funções são implementadas.

Objetos de classe redefinem seu comportamento herdado


Para trabalhar com dados, uma classe de dados precisa de objetos, portanto fornece automaticamente as seguintes implementações para as funções equals, hashCode e toString herdadas da superclasse Qualquer:

A função equals compara valores de propriedade


Ao definir uma classe de dados, sua função igual (e, portanto, o operador ==) ainda retornará verdadeiro se os links apontarem para o mesmo objeto. Mas também retorna true se os objetos tiverem os mesmos valores de propriedade definidos no construtor:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) println(r1.equals(r2)) true 

Os objetos de dados são considerados iguais se suas propriedades contiverem o mesmo valor.

Para objetos iguais, os mesmos valores hashCode são retornados


Se dois objetos de dados forem considerados iguais (em outras palavras, eles tiverem os mesmos valores de propriedade), a função hashCode retornará o mesmo valor para esses objetos:

 val r1 = Recipe("Chicken Bhuna", false) val r2 = Recipe("Chicken Bhuna", false) println(r1.hashCode()) println(r2.hashCode()) 

241131113
241131113

toString retorna os valores de todas as propriedades


Por fim, a função toString não retorna mais o nome da classe, seguido por um número, mas retorna uma string útil com os valores de todas as propriedades definidas no construtor da classe de dados:

 val r1 = Recipe("Chicken Bhuna", false) println(r1.toString()) Recipe(title=Chicken Bhuna, isVegetarian=false) 

Além de substituir funções herdadas da superclasse Qualquer, a classe de dados também fornece ferramentas adicionais que fornecem um trabalho mais eficiente com os dados, por exemplo, a capacidade de copiar objetos de dados. Vamos ver como essas ferramentas funcionam.

Copiando objetos de dados com a função de cópia


Se você precisar criar uma cópia do objeto de dados alterando algumas de suas propriedades, mas deixar as outras propriedades em seu estado original, use a função de cópia. Para fazer isso, a função é chamada para o objeto que você deseja copiar e os nomes de todas as propriedades mutáveis ​​com novos valores são passados ​​para ela.

Suponha que você tenha um objeto Receita chamado r1, que é definido no código assim:

 val r1 = Recipe("Thai Curry", false) 

imagem

Se você deseja criar uma cópia do objeto Recipe, substituindo o valor da propriedade isVegetarian por true, isso é feito da seguinte maneira:

imagem

Em essência, isso significa "criar uma cópia do objeto r1, alterar o valor de sua propriedade isVegetarian para true e atribuir um novo objeto a uma variável chamada r2". Isso cria uma nova cópia do objeto e o objeto original permanece inalterado.

Além da função de cópia, as classes de dados também fornecem um conjunto de funções para dividir um objeto de dados em um conjunto de valores de suas propriedades - esse processo é chamado de desestruturação. Vamos ver como isso é feito.

As classes de dados definem as funções componentN ...


Ao definir uma classe de dados, o compilador adiciona automaticamente à classe um conjunto de funções que podem ser usadas como um mecanismo alternativo para acessar os valores de propriedade do objeto. Essas funções são conhecidas sob o nome geral das funções componentN, em que N é o número de propriedades a serem recuperadas (na ordem da declaração).

Para ver como as funções componentN funcionam, suponha que você tenha o seguinte objeto Receita:

 val r = Recipe("Chicken Bhuna", false) 

Se você deseja obter o valor da primeira propriedade do objeto (propriedade de título), pode chamar a função component1 () do objeto para isso:

 val title = r.component1() 

component1 () retorna a referência que está contida na primeira propriedade definida no construtor da classe de dados.

A função faz o mesmo que o seguinte código:

 val title = r.title 

O código com a função é mais universal. Por que as funções ComponentN são tão úteis nas classes de dados?

... projetado para reestruturar objetos de dados


As funções genéricas componentN são úteis principalmente porque fornecem uma maneira simples e conveniente de dividir um objeto de dados em valores de propriedade ou destruí-lo.

Suponha que você queira pegar os valores das propriedades de um objeto Receita e atribuir o valor de cada uma de suas propriedades a uma variável separada. Em vez de código

 val title = r.title val vegetarian = r.isVegetarian 

com o processamento seqüencial de cada propriedade, você pode usar o seguinte código:

 val (title, vegetarian) = r 

Atribui título à primeira propriedade re vegetariana à segunda propriedade.

Esse código significa "crie duas variáveis, title e vegetarian, e atribua o valor de uma das propriedades de r de cada variável". Ele faz o mesmo que o próximo fragmento

 val title = r.component1() val vegetarian = r.component2() 

mas fica mais compacto.

O operador === sempre verifica se duas variáveis ​​se referem ao mesmo objeto.

Se você deseja verificar se duas variáveis ​​se referem ao mesmo objeto, independentemente de seu tipo, use o operador === em vez de ==. O operador === fornece o resultado verdadeiro se (e somente se) quando duas variáveis ​​contêm uma referência a um objeto real. Se você tiver duas variáveis, xey, e a seguinte expressão:

 x === y 

Como o resultado é verdadeiro, você sabe que as variáveis ​​x e y devem se referir ao mesmo objeto.

Diferentemente do operador ==, o comportamento do operador === é independente da função equals. O operador === sempre se comporta da mesma forma, independentemente do tipo de classe.

Agora que você aprendeu a criar e usar classes de dados, crie um projeto para o código da receita.

Criando um projeto de receitas


Crie um novo projeto Kotlin para a JVM e chame-o de "Receitas". Em seguida, crie um novo
Arquivo Kotlin chamado Recipes.kt: selecione a pasta src, abra o menu Arquivo e selecione o comando
Novo → Arquivo / classe Kotlin. Digite o nome do arquivo “Recipes” e selecione a opção File no grupo Kind.

Adicionamos uma nova classe de dados ao projeto chamada Receita e criamos objetos de dados de receita. Abaixo está o código. Atualize sua versão do Recipes.kt e alinhe-a com a nossa:

 data class Recipe(val title: String, val isVegetarian: Boolean) (  {} ,        .) fun main(args: Array<String>) { val r1 = Recipe("Thai Curry", false) val r2 = Recipe("Thai Curry", false) val r3 = r1.copy(title = "Chicken Bhuna") (  r1    title) println("r1 hash code: ${r1.hashCode()}") println("r2 hash code: ${r2.hashCode()}") println("r3 hash code: ${r3.hashCode()}") println("r1 toString: ${r1.toString()}") println("r1 == r2? ${r1 == r2}") println("r1 === r2? ${r1 === r2}") println("r1 == r3? ${r1 == r3}") val (title, vegetarian) = r1 ( r1) println("title is $title and vegetarian is $vegetarian") } 

Quando você executa seu código, o seguinte texto aparecerá na janela de saída do IDE:

 r1 hash code: -135497891 r2 hash code: -135497891 r3 hash code: 241131113 r1 toString: Recipe(title=Thai Curry, isVegetarian=false) r1 == r2? true r1 === r2? false r1 == r3? false title is Thai Curry and vegetarian is false 


»Mais informações sobre o livro podem ser encontradas no site do editor
» Conteúdo
» Trecho

Desconto de 25% no cupom para Khabrozhitel - Kotlin

Após o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.

Source: https://habr.com/ru/post/pt473614/


All Articles