Java >> Tutoriel Java >  >> Java

Prendre Kotlin pour un tour:Nullable, comparaison d'objets et concision

Récemment, ma femme a acheté un Kindle Fire et j'ai pensé qu'il serait amusant d'écrire une application pour cela. En fait, vous vous souviendrez peut-être que j'essayais depuis longtemps de créer une application de bibliothèque pour elle. Eh bien, quelle meilleure façon de lui donner une autre chance qu'en faisant un tour avec Kotlin.

Développement d'applications mobiles

Ma relation avec le développement d'applications mobiles a été plutôt brève. En fait, ma seule et unique expérience avec elle a été mon dernier semestre de premier cycle en 2016 lorsque j'ai créé une application Android pour interagir avec une serrure intelligente.

À l'époque, je ne connaissais que Java, C, Verilog et x86. Pour mémoire, c'était le répertoire attendu pour quelqu'un poursuivant un diplôme en génie informatique. Quoi qu'il en soit, je n'avais pas beaucoup d'expérience avec quoi que ce soit au-delà de ces langages, j'ai donc choisi la voie Android pour tirer parti de mon expérience Java.

Pour les curieux, nous avons utilisé un Arduino pour piloter une serrure à solénoïde. L'Arduino avait une pièce jointe Bluetooth que nous utilisions pour communiquer avec la serrure via une application mobile Android. Pour être honnête, le projet était assez basique, mais je me suis beaucoup amusé à concevoir quelque chose à partir de zéro avec une équipe multidisciplinaire.

Avance rapide jusqu'à aujourd'hui, et vous constaterez que peu de choses ont changé, du moins pas jusqu'à récemment. En tant que polyglotte, j'ai décidé non seulement de me lancer dans le développement d'applications mobiles, mais aussi de m'essayer à Kotlin.

Revisiter PopLibrary

Début 2016, j'ai décidé de créer une application de bibliothèque, PopLibrary, pour ma petite amie de l'époque, Morgan. Elle voulait quelque chose qu'elle pourrait utiliser pour cataloguer sa collection de livres, afin qu'elle puisse les prêter à ses étudiants comme une bibliothèque.

Le chemin de l'échec

Pour rendre les choses intéressantes, j'ai décidé d'étendre l'outil, afin que je puisse potentiellement en tirer de l'argent. En particulier, je voulais fournir toutes les mêmes fonctionnalités que Morgan souhaitait avec l'ajout de fonctionnalités telles que les recommandations de livres. Ces recommandations seraient ensuite liées à mon compte Amazon Associates, ce qui me rapporterait beaucoup d'argent, du moins c'est ce que je pensais.

Il s'avère qu'en l'espace de deux ans, je n'ai pas pu donner vie à cette application. Je suppose que je n'avais tout simplement pas les compétences nécessaires pour écrire une application complète, et cette réalité ne s'est jamais vraiment installée. Après tout, j'ai essayé d'implémenter PopLibrary à trois reprises :

  • Application Windows en C#
  • Application JavaFX
  • Application Web Laravel

Après trois tentatives, j'ai abandonné. Ensuite, Morgan a acheté un Kindle Fire, et je suis redevenu tout excité. Pour une raison quelconque, j'avais l'impression que les choses pourraient être différentes.

Changement d'exigences

Après avoir échoué trois fois, j'ai décidé cette fois-ci d'implémenter toutes les fonctionnalités souhaitées par Morgan en premier. Ensuite, je vais essayer de voir si je peux gagner un peu d'argent à côté. Cela dit, PopLibrary devrait pouvoir faire les choses suivantes :

  • Afficher une liste des livres que l'utilisateur possède
  • Autoriser l'utilisateur à ajouter et modifier ses propres livres (titre, image, etc.)
  • Persister les données des livres localement (objectif à long terme :stockage dans le cloud)
  • Offrez des fonctionnalités de recherche et de filtrage pour modifier les livres à afficher
  • Autoriser l'utilisateur à prêter des livres à d'autres utilisateurs
  • Utiliser l'appareil photo pour scanner les codes-barres

À ce stade, j'ai déjà implémenté les premières fonctionnalités et je ne travaille sur l'application que depuis environ deux jours. C'est payant d'avoir de l'expérience !

Impressions Kotlin

Cela dit, je suis sûr que vous n'êtes pas ici pour en savoir plus sur mon projet. Vous êtes probablement ici pour un certain nombre de raisons telles que :

  • Savoir si cela vaut la peine de consulter Kotlin
  • Voir ce qu'un débutant pense de la langue
  • Partager quelques difficultés de croissance

Quelle qu'en soit la raison, voici mon point de vue sur Kotlin jusqu'à présent.

Sauvez-vous du zéro

Presque tous les langages avec lesquels j'ai eu le plaisir de jouer (C, C#, Java, Python, JavaScript, PHP, etc.) ont eu cette notion de null . Pour moi, null juste fait sens. Après tout, c'est la valeur parfaite à donner à un type de référence lorsque sa valeur n'existe pas. Par exemple, si vous fournissez un formulaire à un utilisateur et qu'il choisit de ne pas remplir certains des éléments facultatifs, les valeurs de ces éléments sous le capot doivent être null — pas une valeur arbitraire.

Eh bien, du moins, c'était ma compréhension de null . Je ne savais pas que cela pouvait être un tel problème. En fait, il y a eu une tonne de littérature concernant null comme l'une des plus grosses erreurs de l'informatique. Curieusement, je n'avais pas entendu parler de cette animosité envers null jusqu'à ce que j'écrive mon article Hello World in Swift en 2017.

Présentation de Nullable

En raison des problèmes que null peut introduire, de nombreux langages modernes ont essayé de les supprimer. À tout le moins, des langages comme Kotlin et Swift ont enveloppé null dans les objets qui introduit un certain contrôle de sécurité. En d'autres termes, plus de NullPointerExceptions (NPE) à moins que vous ne les demandiez.

Dans Kotlin en particulier, vous pouvez définir n'importe quelle variable sur nullable en utilisant un point d'interrogation :

var count: Int? = null

Ici, nous avons créé une variable appelée count de type Int? ce qui signifie que le nombre peut être un nombre ou null . Lors de l'utilisation de count , vous voudrez peut-être appeler une méthode comme la méthode de décrémentation :

count.dec()

Idéalement, cette méthode décrémenterait count , mais count n'est pas un nombre, c'est null . Dans la plupart des langages, nous aurions un NPE, mais Kotlin ne parviendra pas à compiler. Pour s'adapter à cela, nous devons modifier légèrement la syntaxe :

 count?.dec()

Ici, nous avons effectué un appel sécurisé au count . Si count est null , la chaîne entière renverra null , mais nous n'obtiendrons pas de NPE.

Nullable en pratique

Maintenant, c'est une fonctionnalité géniale pour null haineux, mais j'ai trouvé que cela peut parfois rendre la vie plus difficile. Par exemple, j'ai créé un Book classe qui ressemble à ceci :

data class Book(
    val isbn13: String? = null,
    val title: String? = null,
    val author: String? = null,
    val editor: String? = null,
    val language: String? = null,
    val coverImageURL: String? = null,
    val pageCount: Int? = null,
    val dateOfPublication: Date? = null
) { }

J'ai défini chaque champ sur nullable parce que je ne veux pas remplir ces champs avec des données arbitraires. En d'autres termes, je ne veux pas configurer les champs String comme des chaînes vides ou d'autres données arbitraires car je devrais me rappeler quelle était cette valeur par défaut pour la vérifier plus tard. Au lieu de cela, je laisse tous les champs non remplis comme nuls et traite les problèmes nuls au fur et à mesure.

Cela dit, j'ai rencontré quelques problèmes. Par exemple, si je veux vérifier si une chaîne est contenue dans le titre, je pourrais écrire quelque chose comme ceci :

title?.contains("Gatsby", true)

Bien sûr, le problème ici est que cette expression peut renvoyer true, false ou null. Dans un langage comme JavaScript, les conditions pourraient être en mesure de gérer ce type d'ambiguïté, mais pas dans Kotlin. En conséquence, nous devons essentiellement forcer la valeur null à false en utilisant le Elvis opérateur :

title?.contains("Gatsby", true) ?: false

En d'autres termes, si le titre est null alors l'expression renvoie faux.

Maintenant, imaginez avoir une sorte de condition qui vérifie quelques-uns de ces termes. Très vite, on se retrouve avec une expression désordonnée qui nécessite le Elvis opérateur pour gérer toutes sortes de possibilités nulles. J'ai fini par envelopper l'expression ci-dessus dans une fonction et j'ai enchaîné les différentes possibilités en utilisant l'opérateur OR :

checkContains(title, str)
    || checkContains(author, str)
    || checkContains(editor, str)
    || checkContains(language, str)

Évidemment, ce n'est pas l'idéal, mais il n'y a pas de NPE ! J'imagine que des développeurs Kotlin plus expérimentés auraient une meilleure façon de gérer ce problème, mais j'essaie juste de faire fonctionner une application.

Comparer les objets avec la surcharge de l'opérateur

Bien que la fonctionnalité la plus intéressante de Kotlin pour moi soit la sécurité nulle, je dois dire que la surcharge de l'opérateur est juste derrière. Normalement, je serais totalement contre la surcharge des opérateurs car elle introduit une complexité inutile dans un langage, mais je pense que Kotlin fait un très bon travail avec cette fonctionnalité.

Malheureusement, pour que vous puissiez apprécier la fonctionnalité, vous devez en savoir un peu plus sur le fonctionnement de Java. En particulier, vous devez être familiarisé avec le equals() méthode des objets et le compareTo() méthode de l'interface comparable.

Équivalence d'objet

En Java, tous les objets ont un equals() méthode, afin qu'ils puissent être testés par rapport à un autre objet pour l'égalité. Bien sûr, l'alternative à equals() est le == opérateur, mais il sert un objectif différent. Au lieu de tester si oui ou non deux objets sont équivalents, le == L'opérateur teste si oui ou non deux objets ont la même identité. En d'autres termes, si deux objets ont la même identité, ils sont en fait un objet avec plusieurs alias.

Dans Kotlin, le == est utilisé universellement pour l'égalité. Pendant ce temps, la vérification d'identité est gérée avec le === opérateur. En conséquence, == et equals() sont synonymes. Une fois que nous avons implémenté le equals() méthode, nous pouvons utiliser le == opérateur à sa place :

val x = Date(1000)
val y = Date(1000)
x.equals(y) // Evaluates equality based on equals() implementation 
x == y // Does the same exact thing

En fait, IntelliJ promeut en fait l'utilisation de l'opérateur plutôt que la méthode, et je suis un grand fan. Mais attendez, ça va mieux !

Comparaison d'objets

En Java, lorsque nous voulons comparer deux objets, par exemple à des fins de tri, nous nous assurons généralement d'implémenter le Comparable interface. Dans le cadre de cette interface, nous devons remplacer le compareTo() méthode qui prend une paire d'objets et renvoie un nombre qui représente leur relation. Lorsque les deux objets sont équivalents, la méthode doit renvoyer 0. Pendant ce temps, la méthode doit renvoyer un nombre positif lorsque l'objet appelant est l'objet "plus grand" et un nombre négatif dans le cas contraire.

Déterminer quel objet est "plus grand" dépend du type d'objet que nous utilisons. Par exemple, la chaîne "pomme" est plus petite que la chaîne "carotte" car l'ordre alphabétique dicte que "pomme" vient en premier. En d'autres termes, compareTo doit se comporter comme suit :

"apple".compareTo("carrot") // Returns some negative number
"carrot".compareTo("apple") // Returns some positive number

Quoi qu'il en soit, compareTo est un peu déroutant, et Kotlin fait un bon travail pour atténuer une partie de cette confusion en introduisant quelques opérateurs. En utilisant le même exemple que ci-dessus, nous pouvons comparer "pomme" et "carotte" en utilisant les opérateurs relationnels :

"apple" > "carrot" // false
"apple" < "carrot" // true

Personnellement, je l'ai utilisé pour trier les livres par leur niveau Lexile. Dans mon projet, Lexile est une classe qui implémente Comparable . Pour les comparer, j'utilise leur valeur numérique :

override fun compareTo(other: Lexile): Int {
    return this.toInteger() - other.toInteger()
}

Ensuite, je peux comparer deux Lexile objets comme suit :

val lex1 = Lexile(270, Lexile.LexileType.NA)
val lex2 = Lexile(400, Lexile.LexileType.NA)
assertTrue(lex1 < lex2)

Maintenant, je pense que c'est plutôt cool.

Dites adieu à la verbosité

L'une des plus grandes plaintes des gens à propos de Java est la verbosité du langage. En particulier, les définitions de variables nécessitent une quantité écrasante de détails :

ArrayList<Integer> myList = new ArrayList<Integer>()

Afin de créer cette liste, nous avons dû spécifier de nombreuses informations :

  • Tapez, deux fois
  • Type générique, deux fois
  • Nom
  • Mot clé (nouveau)
  • Opérateur (=)
  • Constructeur

Naturellement, cette ligne de code peut croître de façon exponentielle en fonction de facteurs tels que la longueur du nom du type, le nombre de types génériques imbriqués et la taille du constructeur.

Pour faire face à cela, Kotlin introduit une syntaxe beaucoup plus concise :

val list = arrayListOf<Int>()

De toute évidence, il se passe beaucoup de choses ici, mais il est important de noter le manque d'informations redondantes. Nous ne spécifions pas le type, mais nous avons l'option. Aussi, remplir le ArrayList est nettement plus simple :

val list = arrayListOf<Int>(5, 6, 8, -4)

Bien que la verbosité réduite soit agréable, je voudrais également souligner que Kotlin a également introduit deux nouveaux mots-clés :val et var . Nous utilisons val lorsque nous voulons marquer une variable comme immuable ou en lecture seule (pensez final de Java) et var pour marquer une variable comme modifiable.

Maîtriser l'art du contrôle de flux

S'il y a quelque chose que j'ai appris en jouant avec les langages de programmation, il existe une tonne de mécanismes de contrôle de flux. Par exemple, il y a des instructions if et des boucles juste pour les débutants. Ensuite, il existe des mécanismes amusants comme les instructions goto et switch qui offrent encore plus d'options pour le contrôle de flux.

Cela dit, Kotlin m'a présenté un autre mécanisme de contrôle de flux :when . C'est essentiellement un switch déclaration, mais je trouve la syntaxe beaucoup plus propre :

override fun toString(): String {
    return when (this.type) {
        LexileType.NA -> level.toString() + "L"
        else -> type.name + level.toString() + "L"
    }
}

Dans cette méthode, nous avons remplacé le toString() méthode pour renvoyer une chaîne sous deux conditions possibles :

  • Le type est NA
  • Le type est n'importe quoi d'autre

En particulier, on renvoie le résultat d'un when déclaration qui accepte le type de cet objet (même Lexile classe de tout à l'heure). Si le type est NA, nous renvoyons une chaîne. Sinon, nous renvoyons une autre chaîne.

A mon avis, le when est intelligente car elle supprime beaucoup de code redondant que vous pourriez trouver dans un switch instruction :break, return, etc. Naturellement, je les ai beaucoup utilisées car IntelliJ les préfère en fait aux chaînes d'instructions if. Aussi, je pense juste qu'ils sont cool.

Le verdict

En ce moment, j'aime beaucoup Kotlin. La fonction de sécurité nulle a été difficile à contourner, mais tout le reste est excellent. Kotlin est tout ce que j'aime à propos de Java et tout ce que j'aime à propos des langages de niveau supérieur comme Python. Avec la plupart du passe-partout à l'écart, j'ai l'impression que je peux vraiment construire quelque chose rapidement tout en m'appuyant sur tous les incroyables utilitaires d'analyse statique fournis en standard avec les langages compilés.

Cela dit, nous verrons bientôt comment je me sens. Je suis probablement juste en phase de lune de miel, mais j'apprécie vraiment cette langue. Cela me rappelle en quelque sorte ce que j'ai ressenti lorsque j'ai commencé à utiliser C# - les deux sont de grandes améliorations par rapport à Java.

Comme c'est la première fois que j'examine vraiment un langage en profondeur comme celui-ci, je n'ai pas vraiment d'articles à recommander. Quoi qu'il en soit, voici quelques articles que j'aimerais voir davantage :

  • Ciseaux à papier de roche utilisant l'arithmétique modulaire
  • Réflexion sur mon premier semestre d'enseignement

Aussi, si cet article vous a plu, partagez-le ! Au moment de sa publication, j'aurai probablement une opinion totalement différente sur le sujet, alors dialoguons également. Pour ceux d'entre vous qui souhaitent créer une communauté, rendez-vous sur la page des membres et inscrivez-vous ! Pour les moins engagés, il y a aussi une newsletter. Comme toujours, merci d'être passé !


Balise Java