Le livre «Head First. Kotlin »

image Salut, habrozhiteli! Nous avons un livre publié pour étudier Kotlin en utilisant la technique Head First, qui va au-delà de la syntaxe et des instructions pour résoudre des problÚmes spécifiques. Ce livre vous donnera tout ce dont vous avez besoin - des bases du langage aux méthodes avancées. Et vous pouvez pratiquer la programmation orientée objet et fonctionnelle.

Un extrait "Data Classes" est présenté sous la coupe.

Travailler avec des données


Personne ne veut perdre de temps et refaire ce qui a dĂ©jĂ  Ă©tĂ© fait. La plupart des applications utilisent des classes pour stocker des donnĂ©es. Pour simplifier le travail, les crĂ©ateurs de Kotlin ont proposĂ© le concept d'une classe de donnĂ©es. Dans ce chapitre, vous apprendrez comment les classes de donnĂ©es vous aident Ă  Ă©crire du code plus Ă©lĂ©gant et concis dont vous ne pouviez que rĂȘver auparavant. Nous allons examiner les fonctions d'assistance des classes de donnĂ©es et apprendre Ă  dĂ©composer un objet de donnĂ©es en composants. Dans le mĂȘme temps, nous vous expliquerons comment les valeurs des paramĂštres par dĂ©faut rendent le code plus flexible et vous prĂ©senterons Ă©galement Any, l'ancĂȘtre de toutes les superclasses.

L'opérateur == appelle une fonction appelée égal


Comme vous le savez dĂ©jĂ , l'opĂ©rateur == peut ĂȘtre utilisĂ© pour vĂ©rifier l'Ă©galitĂ©. Chaque fois que l'instruction == est exĂ©cutĂ©e, une fonction appelĂ©e Ă©gal est appelĂ©e. Chaque objet contient une fonction Ă©gale et l'implĂ©mentation de cette fonction dĂ©termine le comportement de l'opĂ©rateur ==.

Par dĂ©faut, la fonction Ă©gal pour vĂ©rifier l'Ă©galitĂ© vĂ©rifie si deux variables font rĂ©fĂ©rence au mĂȘme objet.

Pour comprendre comment cela fonctionne, imaginez deux variables Wolf nommées w1 et w2. Si w1 et w2 contiennent des références à un objet Wolf, lors de leur comparaison avec l'opérateur ==, le résultat est vrai:

image

Mais si w1 et w2 contiennent des rĂ©fĂ©rences Ă  des objets Wolf diffĂ©rents, leur comparaison avec l'opĂ©rateur == donne le rĂ©sultat faux, mĂȘme si les objets contiennent les mĂȘmes valeurs de propriĂ©tĂ©.

image

Comme mentionnĂ© prĂ©cĂ©demment, la fonction equals est automatiquement incluse dans chaque objet que vous crĂ©ez. Mais d'oĂč vient cette fonction?

est égal à hérite de la superclasse Any


Chaque objet contient une fonction appelĂ©e Ă©gal car sa classe hĂ©rite d'une fonction d'une classe nommĂ©e Any. La classe Any est l'ancĂȘtre de toutes les classes: la superclasse rĂ©sultante de tout. Chaque classe que vous dĂ©finissez est une sous-classe de Any, et vous n'avez pas besoin de le signaler dans le programme. Ainsi, si vous Ă©crivez un code de classe appelĂ© myClass, qui ressemble Ă  ceci:

class MyClass { ... } 

Le compilateur le convertira automatiquement au format suivant:
image

Chaque classe est une sous-classe de Any et hérite de son comportement. Chaque classe est une sous-classe de Any, et vous n'avez pas à le signaler dans le programme.

L'importance de tout héritage


Inclure Any en tant que superclasse résultante présente deux avantages importants:

  • Il garantit que chaque classe hĂ©rite d'un comportement commun. La classe Any dĂ©finit un comportement important dont dĂ©pend le fonctionnement du systĂšme. Et puisque chaque classe est une sous-classe de Any, ce comportement est hĂ©ritĂ© par tous les objets que vous crĂ©ez. Ainsi, la classe Any dĂ©finit une fonction appelĂ©e Ă©gal, et par consĂ©quent, cette fonction est automatiquement hĂ©ritĂ©e par tous les objets.
  • Cela signifie que le polymorphisme peut ĂȘtre utilisĂ© avec n'importe quel objet. Chaque classe est une sous-classe de Any, donc tout objet que vous crĂ©ez a la classe Any comme super-type final. Cela signifie que vous pouvez crĂ©er une fonction avec des paramĂštres Any ou un type de retour Any qui fonctionnera avec des objets de tout type. Cela signifie Ă©galement que vous pouvez crĂ©er des tableaux polymorphes pour stocker des objets de tout type avec du code de la forme suivante:

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

Le compilateur remarque que chaque objet du tableau a un prototype commun de Any et crée donc un tableau de type Array.

Le comportement général hérité de la classe Any mérite un examen plus approfondi.

Comportement commun hérité de Any


La classe Any définit plusieurs fonctions héritées par chaque classe. Voici des exemples de fonctions de base et de leur comportement:

  • Ă©gal (tout: Tout): boolĂ©en
    Vérifie si deux objets sont considérés comme «égaux». Par défaut, la fonction renvoie true si elle est utilisée pour vérifier un objet, ou false - pour différents objets. Dans les coulisses, la fonction d'égalité est appelée chaque fois que l'opérateur == est utilisé dans le programme.

 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
    Renvoie un code de hachage pour un objet. Les codes de hachage sont souvent utilisés par certaines structures de données pour stocker et récupérer efficacement les valeurs.

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

523429237 (valeur du code de hachage w)

  • toString (): String
    Renvoie un message String représentant l'objet. Par défaut, le message contient le nom de la classe et un numéro, ce qui ne nous intéresse généralement pas.

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

Wolf @ 1f32e575

Par dĂ©faut, la fonction equals vĂ©rifie si deux objets sont le mĂȘme objet rĂ©el.

La fonction equals détermine le comportement de l'opérateur ==.

La classe Any fournit une implémentation par défaut pour toutes les fonctions répertoriées, et ces implémentations sont héritées par toutes les classes. Cependant, vous pouvez remplacer ces implémentations pour modifier le comportement par défaut de toutes les fonctions répertoriées.

VĂ©rification simple de l'Ă©quivalence de deux objets


Dans certaines situations, vous devez modifier l'implémentation de la fonction equals pour modifier le comportement de l'opérateur ==.

Supposons que vous ayez une classe de recette qui vous permet de crĂ©er des objets pour stocker des recettes. Dans une telle situation, vous considĂ©rerez probablement deux objets Recette Ă©gaux (ou Ă©quivalents) s'ils contiennent une description de la mĂȘme recette. Disons que la classe Recipe est dĂ©finie avec deux propriĂ©tĂ©s - title et isVegetarian:

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

L'opĂ©rateur == retournera true s'il est utilisĂ© pour comparer deux objets Recipe avec les mĂȘmes propriĂ©tĂ©s, title et isVegetarian:

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

image

Bien que vous puissiez changer le comportement de l'opérateur == en écrivant du code supplémentaire pour remplacer la fonction equals, les développeurs de Kotlin ont fourni une solution plus pratique: ils ont créé le concept d'une classe de données. Voyons ce que sont ces classes et comment elles sont créées.

La classe de données vous permet de créer des objets de données.


Une classe de donnĂ©es est une classe de crĂ©ation d'objets pour le stockage de donnĂ©es. Il comprend des outils utiles pour travailler avec des donnĂ©es - par exemple, une nouvelle implĂ©mentation de la fonction equals, qui vĂ©rifie si deux objets de donnĂ©es contiennent les mĂȘmes valeurs de propriĂ©tĂ©. Si deux objets contiennent les mĂȘmes donnĂ©es, ils peuvent ĂȘtre considĂ©rĂ©s comme Ă©gaux.

Pour définir une classe de données, faites précéder la définition de données habituelle avec le mot-clé data. Le code suivant convertit la classe de recette créée précédemment en une classe de données:

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

Le préfixe de données convertit une classe réguliÚre en classe de données.

Comment créer des objets basés sur une classe de données


Les objets de classe de donnĂ©es sont crĂ©Ă©s de la mĂȘme maniĂšre que les objets de classe standard: en appelant le constructeur de cette classe. Par exemple, le code suivant crĂ©e un nouvel objet de donnĂ©es de recette et l'affecte Ă  une nouvelle variable nommĂ©e r1:

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

Les classes de donnĂ©es remplacent automatiquement leurs fonctions Ă©gales pour modifier le comportement de l'opĂ©rateur == afin que l'Ă©galitĂ© des objets soit vĂ©rifiĂ©e en fonction des valeurs de propriĂ©tĂ© de chaque objet. Si, par exemple, vous crĂ©ez deux objets Recette avec les mĂȘmes valeurs de propriĂ©tĂ©, la comparaison des deux objets avec l'opĂ©rateur == donnera le rĂ©sultat vrai, car les mĂȘmes donnĂ©es y sont stockĂ©es:

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

r1 et r2 sont considĂ©rĂ©s comme «égaux» car deux objets Recette contiennent les mĂȘmes donnĂ©es.

En plus de la nouvelle implémentation de la fonction equals héritée de la superclasse Any, les classes de données
remplacer également les fonctions hashCode et toString. Voyons comment ces fonctions sont implémentées.

Les objets de classe redéfinissent leur comportement hérité


Pour travailler avec des données, une classe de données a besoin d'objets, elle fournit donc automatiquement les implémentations suivantes pour les fonctions equals, hashCode et toString héritées de la superclasse Any:

La fonction equals compare les valeurs des propriétés


Lors de la dĂ©finition d'une classe de donnĂ©es, sa fonction Ă©gale (et donc l'opĂ©rateur ==) renvoie toujours true si les liens pointent vers le mĂȘme objet. Mais elle renvoie Ă©galement true si les objets ont les mĂȘmes valeurs de propriĂ©tĂ© dĂ©finies dans le constructeur:

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

Les objets de donnĂ©es sont considĂ©rĂ©s comme Ă©gaux si leurs propriĂ©tĂ©s contiennent la mĂȘme valeur.

Pour des objets Ă©gaux, les mĂȘmes valeurs hashCode sont retournĂ©es


Si deux objets de donnĂ©es sont considĂ©rĂ©s comme Ă©gaux (en d'autres termes, ils ont les mĂȘmes valeurs de propriĂ©tĂ©), la fonction hashCode renvoie la mĂȘme valeur pour ces objets:

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

241131113
241131113

toString renvoie les valeurs de toutes les propriétés


Enfin, la fonction toString ne renvoie plus le nom de la classe, suivi d'un nombre, mais renvoie une chaßne utile avec les valeurs de toutes les propriétés définies dans le constructeur de la classe de données:

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

En plus de remplacer les fonctions héritées de la superclasse Any, la classe de données fournit également des outils supplémentaires qui permettent un travail plus efficace avec les données, par exemple, la possibilité de copier des objets de données. Voyons comment fonctionnent ces outils.

Copie d'objets de données avec la fonction de copie


Si vous devez créer une copie de l'objet de données en modifiant certaines de ses propriétés, mais en laissant les autres propriétés dans leur état d'origine, utilisez la fonction de copie. Pour ce faire, la fonction est appelée pour l'objet que vous souhaitez copier et les noms de toutes les propriétés modifiables avec de nouvelles valeurs lui sont transmis.

Supposons que vous ayez un objet Recipe nommé r1, qui est défini dans le code comme ceci:

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

image

Si vous souhaitez créer une copie de l'objet Recipe, en remplaçant la valeur de la propriété isVegetarian par true, cela se fait comme suit:

image

Essentiellement, cela signifie «créer une copie de l'objet r1, changer la valeur de sa propriété isVegetarian sur true et affecter un nouvel objet à une variable nommée r2». Cela crée une nouvelle copie de l'objet et l'objet d'origine reste inchangé.

En plus de la fonction de copie, les classes de données fournissent également un ensemble de fonctions pour diviser un objet de données en un ensemble de valeurs de ses propriétés - ce processus est appelé déstructuration. Voyons comment cela se fait.

Les classes de données définissent les fonctions componentN ...


Lors de la dĂ©finition d'une classe de donnĂ©es, le compilateur ajoute automatiquement Ă  la classe un ensemble de fonctions qui peuvent ĂȘtre utilisĂ©es comme mĂ©canisme alternatif pour accĂ©der aux valeurs des propriĂ©tĂ©s des objets. Ces fonctions sont connues sous le nom gĂ©nĂ©ral des fonctions componentN, oĂč N est le nombre de propriĂ©tĂ©s Ă  rĂ©cupĂ©rer (dans l'ordre de dĂ©claration).

Pour voir comment fonctionnent les fonctions componentN, supposons que vous ayez l'objet Recipe suivant:

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

Si vous souhaitez obtenir la valeur de la premiÚre propriété de l'objet (propriété title), vous pouvez appeler la fonction component1 () de l'objet pour cela:

 val title = r.component1() 

component1 () renvoie la référence contenue dans la premiÚre propriété définie dans le constructeur de la classe de données.

La fonction fait la mĂȘme chose que le code suivant:

 val title = r.title 

Le code avec la fonction est plus universel. Pourquoi les fonctions ComponentN sont-elles si utiles dans les classes de données?

... conçu pour restructurer les objets de données


Les fonctions génériques de componentN sont utiles principalement parce qu'elles fournissent un moyen simple et pratique de diviser un objet de données en valeurs de propriété ou de le détruire.

Supposons que vous souhaitiez prendre les valeurs des propriétés d'un objet Recette et affecter la valeur de chacune de ses propriétés à une variable distincte. Au lieu de code

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

avec le traitement séquentiel de chaque propriété, vous pouvez utiliser le code suivant:

 val (title, vegetarian) = r 

Attribue un titre à la premiÚre propriété r et végétarien à la deuxiÚme propriété.

Ce code signifie "crĂ©er deux variables, titre et vĂ©gĂ©tarien, et attribuer la valeur de l'une des r propriĂ©tĂ©s de chaque variable." Il fait la mĂȘme chose que le prochain fragment

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

mais il s'avĂšre plus compact.

L'opĂ©rateur === vĂ©rifie toujours si deux variables se rĂ©fĂšrent au mĂȘme objet.

Si vous souhaitez vĂ©rifier si deux variables font rĂ©fĂ©rence au mĂȘme objet quel que soit leur type, utilisez l'opĂ©rateur === au lieu de ==. L'opĂ©rateur === donne le rĂ©sultat vrai si (et seulement si) lorsque deux variables contiennent une rĂ©fĂ©rence Ă  un objet rĂ©el. Si vous avez deux variables, x et y, et l'expression suivante:

 x === y 

donne le rĂ©sultat vrai, alors vous savez que les variables x et y doivent faire rĂ©fĂ©rence au mĂȘme objet.

Contrairement Ă  l'opĂ©rateur ==, le comportement de l'opĂ©rateur === est indĂ©pendant de la fonction Ă©gal. L'opĂ©rateur === se comporte toujours de la mĂȘme maniĂšre quel que soit le type de classe.

Maintenant que vous avez appris à créer et à utiliser des classes de données, créez un projet pour le code de recette.

Création d'un projet de recettes


Créez un nouveau projet Kotlin pour la JVM et nommez-le «Recettes». Ensuite, créez un nouveau
Fichier Kotlin nommé Recipes.kt: sélectionnez le dossier src, ouvrez le menu Fichier et sélectionnez la commande
Nouveau → Fichier / classe Kotlin. Saisissez le nom de fichier «Recettes» et sĂ©lectionnez l'option Fichier dans le groupe Type.

Nous ajoutons une nouvelle classe de données au projet appelé Recette et créons des objets de données Recette. Voici le code. Mettez à jour votre version de Recipes.kt et alignez-la avec la nÎtre:

 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") } 

Lorsque vous exĂ©cutez votre code, le texte suivant apparaĂźt dans la fenĂȘtre de sortie de l'EDI:

 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 


»Plus d'informations sur le livre sont disponibles sur le site Web de l'éditeur
» Contenu
» Extrait

25% de réduction sur le coupon pour Khabrozhitel - Kotlin

Lors du paiement de la version papier du livre, un livre électronique est envoyé par e-mail.

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


All Articles