Chapitre 14: Mélanger OOP et FP: comparer Java 8 et Scala

Inspectons maintenant la signature du filtre de méthode intégré:Chapitre 15. Mélanger OOP et FP: comparer Java 8 et Scala

Ce chapitre couvre

  • Une introduction à Scala
  • Comment Java 8 se rapporte à Scala et vice versa
  • Comment les fonctions de Scala se comparent à Java 8
  • Classes et traits

Scala est un langage de programmation qui mélange la programmation orientée objet et la programmation fonctionnelle. Il est souvent considéré comme un langage alternatif à Java pour les programmeurs qui veulent des fonctionnalités dans un langage de programmation statiquement typé qui s’exécute sur la JVM tout en gardant une impression Java. Scala introduit beaucoup plus de fonctionnalités par rapport à Java: un système de type plus sophistiqué, l’inférence de type, le pattern matching (comme présenté dans la section 14.4), construit pour définir simplement des langages spécifiques au domaine, etc. En outre, vous pouvez accéder à toutes les bibliothèques Java dans le code Scala.

Vous vous demandez peut-être pourquoi nous avons un chapitre sur Scala dans un livre Java 8. Ce tutoriel a été largement axé sur l’adoption de la programmation fonctionnelle en Java. Scala, tout comme Java 8, prend en charge les concepts de traitement de style fonctionnel des collections (c’est-à-dire des opérations de type flux), des fonctions de première classe et des méthodes par défaut. Mais Scala pousse ces idées plus loin: il fournit un plus grand nombre de fonctionnalités autour de ces idées par rapport à Java 8. Nous pensons qu’il peut être intéressant de comparer Scala avec l’approche adoptée par Java 8 et de voir les limitations de Java 8. Ce chapitre vise à éclairer cette question pour apaiser votre curiosité.

Gardez à l’esprit que le but de ce chapitre n’est pas de vous apprendre à écrire du code Scala. Il existe de nombreuses fonctionnalités telles que le pattern matching, supportés dans Scala mais pas dans Java, que nous ne discuterons pas. Au lieu de cela, ce chapitre se concentre sur la comparaison des nouvelles fonctionnalités de Java 8 à celles fournies par Scala, afin que vous ayez une idée de la situation dans son ensemble. Par exemple, vous trouverez que vous pouvez écrire du code plus concis et plus lisible dans Scala par rapport à Java.

Ce chapitre commence par une introduction à Scala: comment écrire des programmes simples et travailler avec des collections. Ensuite, nous discutons des fonctions dans Scala: fonctions de première classe, fermetures et curry. Enfin, nous examinons les classes de Scala et une fonction appelée traits: la position de Scala sur les interfaces et les méthodes par défaut.

15.1. Introduction à Scala

Cette section présente brièvement les fonctionnalités de base de Scala afin que vous puissiez avoir une idée des programmes simples de Scala. Nous commençons avec un exemple « Hello world » légèrement modifié écrit dans un style impératif et un style fonctionnel. Nous examinons ensuite certaines structures de données supportées par Scala – List, Set, Map, Stream, Tuple et Option – et les comparons à Java 8. Enfin, nous présentons les traits, le remplacement de Scala pour les interfaces de Java, qui supportent également l’héritage des méthodes à l’instanciation de l’objet.

15.1.1. Hello beer

Regardons un exemple simple pour vous faire une idée de la syntaxe et des fonctionnalités de Scala et comment elles se comparent à Java. Pour changer un peu de l’exemple classique « Bonjour tout le monde », apportons de la bière. Vous voulez imprimer la sortie suivante à l’écran:

Scala de style impératif

Voici à quoi le code pour imprimer cette sortie ressemble en Scala, en utilisant un style impératif:

Vous trouverez des informations sur la façon d’exécuter ce code sur le site officiel de Scala.  Ce programme ressemble beaucoup à ce que vous écririez en Java. Il a une structure similaire aux programmes Java: il consiste en une méthode appelée main, qui prend un tableau de chaînes en argument (les annotations de type suivent la syntaxe s:String au lieu de String s comme en Java). La méthode principale ne renvoie pas de valeur et il n’est donc pas nécessaire de déclarer un type de retour dans Scala comme vous le feriez en Java en utilisant void.



En général, les déclarations de méthode non-récurrentes dans Scala n’ont pas besoin d’un type de retour explicite car Scala peut l’inférer pour vous.



Avant de regarder le corps de la méthode principale, nous devons discuter de la déclaration d’objet. Après tout, en Java, vous devez déclarer la méthode main au sein d’une classe. L’objet de déclaration introduit un objet singleton: il déclare une classe Beer et l’instancie en même temps. Une seule instance est créée. Ceci est le premier exemple d’un design pattern classique (le pattern singleton) implémenté de la façon la plus simple qui soit, (out of the box)! En outre, vous pouvez voir les méthodes dans une déclaration d’objet comme étant déclarées comme statiques; c’est pourquoi la signature de la méthode principale n’est pas explicitement déclarée comme statique.

Regardons maintenant le corps du main. Il ressemble également à Java, mais les instructions n’ont pas besoin de se terminer par un point-virgule (facultatif). Le corps consiste en une boucle while, qui incrémente une variable mutable, n. Pour chaque nouvelle valeur de n, vous imprimez une chaîne à l’écran en utilisant la méthode prédéfinie println. La ligne println présente une autre fonctionnalité disponible dans Scala: l’interpolation de String. L’interpolation de String vous permet d’intégrer des variables et des expressions directement dans des littéraux de chaîne. Dans le code précédent, vous pouvez utiliser la variable n directement dans la chaîne littérale s « Hello $ {n} bouteilles de bière ». Normalement en Java, vous devez faire une concaténation explicite comme « Hello » + n + « bouteilles de bière ».

Style fonctionnel en Scala

Mais que peut vraiment offrir Scala après tout ce que nous avons dit sur la programmation de style fonctionnel tout au long de ce tutoriel? Le code précédent peut être écrit sous une forme plus fonctionnelle comme suit dans Java 8:

Voici à quoi ressemble Scala:

Il ressemble au code Java mais est moins verbeux. D’abord, vous pouvez créer une plage en utilisant les expressions 2 à 6. Voici quelque chose de cool: 2 est un objet de type Int. En Scala, tout est un objet; il n’y a pas de concept de type primitif comme Java. Cela fait de Scala un langage orienté objet complet. Un objet Int dans Scala prend en charge une méthode nommée to, qui prend comme argument un autre Int et renvoie une plage. Donc, vous auriez pu écrire 2.to (6) à la place. Mais les méthodes qui prennent un argument peuvent être écrites sous forme d’infixe. Ensuite, foreach (avec un e minuscule) est similaire à forEach dans Java 8 (avec un E majuscule). C’est une méthode disponible sur une plage (ici, vous utilisez à nouveau la notation infix), et il faut une expression lambda comme argument à appliquer sur chaque élément. La syntaxe de l’expression lambda est similaire à Java 8 mais la flèche est => au lieu de ->.  Le code précédent est fonctionnel: vous ne mutez pas une variable comme vous l’avez fait dans notre exemple précédent en utilisant une boucle while.

15.1.2. Structures de données basique: Liste, Set, Map, Tuple, Stream, Option

La plupart des vrais programmes ont besoin de manipuler et stocker des données, alors regardons maintenant comment vous pouvez manipuler les collections dans Scala et comment cela se compare à Java 8.

Créer des collections

Créer des collections dans Scala est simple, grâce à l’accent mis sur la concision. Pour illustrer, voici comment créer une Map:

Plusieurs choses sont nouvelles avec cette ligne de code. Tout d’abord, c’est génial que vous puissiez créer une map et associer directement une clé à une valeur, en utilisant la syntaxe ->. Il n’y a pas besoin d’ajouter des éléments manuellement comme dans Java:

Il y a des discussions pour ajouter du sucre syntaxique similaire dans les futures versions de Java, mais ce n’est pas disponible dans Java 8.  La deuxième nouveauté est que vous pouvez choisir de ne pas annoter le type de la variable authorsToAge. Vous auriez pu écrire explicitement  val authors-ToAge: Map [String, Int], mais Scala peut déduire le type de la variable pour vous. (Notez que le code est toujours vérifié statiquement! Toutes les variables ont un type donné à la compilation.) Nous reviendrons sur cette fonctionnalité plus tard dans le chapitre. Troisièmement, vous utilisez le mot-clé val au lieu de var. Quelle est la différence? Le mot clé val signifie que la variable est en lecture seule et ne peut pas être réaffectée à (tout comme la finale en Java). Le mot-clé var signifie que la variable est en lecture-écriture.

Qu’en est-il des autres collections? Vous pouvez créer une liste (une liste unique) ou un ensemble (pas de doublons, set) aussi facilement:

La variable authors aura trois éléments et la variable numbers aura cinq éléments.

Immutable vs. mutable

Une propriété importante à garder à l’esprit est que les collections créées précédemment sont immuables par défaut. Cela signifie qu’ils ne peuvent pas être modifiés après leur création. Ceci est utile car vous savez que l’accès à la collection à tout moment de votre programme donnera toujours une collection avec les mêmes éléments.

Alors, comment pouvez-vous mettre à jour une collection immuable dans Scala? Pour revenir à la terminologie utilisée dans le chapitre précédent, on dit que de telles collections dans Scala sont persistantes: mettre à jour une collection produit une nouvelle collection qui partage autant que possible avec sa version précédente, qui persiste sans être affectée par des changements comme nous l’avons montré aux figures 14.3 et 14.4. En conséquence de cette propriété, votre code aura moins de dépendances de données implicites: il y a moins de confusion sur l’emplacement de votre code qui met à jour une collection (ou toute autre structure de données partagée) et à quel moment.

Regardons un exemple pour démontrer cette idée. Ajoutons un élément à un ensemble:

Dans cet exemple, l’ensemble des nombres n’est pas modifié. Au lieu de cela, un nouvel ensemble est créé avec un élément supplémentaire.

Notez que Scala ne vous oblige pas à utiliser des collections immuables – cela facilite l’adoption de l’immuabilité dans votre code. Il existe également des versions mutables disponibles dans le package scala.collection.mutable.



Non modifiable vs. immuable

Java fournit plusieurs façons de créer des collections non modifiables. Dans le code suivant, la variable newNumbers est une vue en lecture seule des nombres définis:

Cela signifie que vous ne pourrez pas ajouter de nouveaux éléments via la variable newNumbers. Mais une collection non modifiable est juste un emballage sur une collection modifiable. Cela signifie que vous pouvez toujours ajouter des éléments en accédant à la variable des nombres!

En revanche, les collections immuables garantissent que rien ne peut changer la collection, quel que soit le nombre de variables qui la pointent.

Nous avons expliqué au chapitre 14 comment créer une structure de données persistante: une structure de données immuable qui préserve la version précédente de lui-même lorsqu’elle est modifiée. Toutes les modifications produisent toujours une nouvelle structure mise à jour.



Travailler avec des collections

Maintenant que vous avez vu comment créer des collections, vous devez savoir ce que vous pouvez en faire. Il s’avère que les collections dans Scala prennent en charge des opérations similaires à celles fournies par l’API Streams. Par exemple, vous reconnaîtrez le filtre et la map dans l’exemple suivant et comme illustré dans la figure 15.1:

Figure 15.1. Opérations similaires à des flux avec les listes en Scala

Ne vous inquiétez pas pour la première ligne; il transforme fondamentalement un fichier en une liste de chaînes constituées des lignes du fichier (similaire à ce que Files.readAllLines fournit dans Java 8). La deuxième ligne crée un pipeline de deux opérations:

  • Une opération de filtrage qui sélectionne uniquement les lignes dont la longueur est supérieure à 10
  • Une opération de carte qui transforme ces longues lignes en majuscules

Ce code peut aussi être écrit comme suit:

Vous utilisez la notation infixée ainsi que le trait de soulignement (_), qui est un espace réservé qui correspond positionnellement à tous les arguments. Dans ce cas, vous pouvez lire _.length () comme l => l.length (). Dans les fonctions passées à filter et à map. le trait de soulignement est lié au paramètre de ligne à traiter.

Il y a beaucoup plus d’opérations utiles disponibles dans l’API de collection de Scala. Nous vous recommandons de regarder la documentation de Scala pour avoir une idée. Notez qu’il est légèrement plus riche que ce que l’API Streams fournit (par exemple, il existe un support pour les opérations de zipping, qui vous permettent de combiner des éléments de deux listes), donc vous apprendrez certainement quelques idiomes de programmation en le vérifiant. Ces idiomes peuvent également faire partie de l’API Streams dans les futures versions de Java.

Enfin, rappelez-vous que dans Java 8, vous pouvez demander l’exécution simultanée d’un pipeline en appelant parallèlement un flux. Scala a une astuce similaire; il suffit d’utiliser la méthode par:

Tuples

Regardons maintenant une autre fonctionnalité qui est souvent douloureusement verbeuse en Java: les tuples. Vous pouvez utiliser des tuples pour grouper les gens par leur nom et leur numéro de téléphone (ici des paires simples) sans déclarer une nouvelle classe et instancier un objet pour cela: (« Raoul », « + 44 007007007 »), (« Alan « , » +44 003133700 « ), et ainsi de suite.

Malheureusement, Java ne fournit pas de support pour les tuples. Vous devez donc créer votre propre structure de données. Voici une simple classe Pair:

Et bien sûr, vous devez instancier les paires explicitement:

D’accord, mais que diriez-vous des triplets? Que diriez-vous des tuples de taille arbitraire? Cela devient vraiment fastidieux et finira par affecter la lisibilité et la maintenance de vos programmes.

Scala fournit des littéraux en tuple, ce qui signifie que vous pouvez créer des tuples avec du sucre syntaxique simple – juste la notation mathématique normale:

Scala prend en charge des tuples de taille arbitraire, donc tout ce qui suit est possible:

Vous pouvez accéder aux éléments des tuples par leurs positions en utilisant les accesseurs _1, _2 (commençant à 1), par exemple:

N’est-ce pas plus agréable que ce que vous auriez à écrire en Java? La bonne nouvelle est qu’il y a des discussions sur l’introduction des littéraux en tuple dans les futures versions de Java (voir le chapitre 15 pour plus de détails).

Stream

Les collections que nous avons décrites jusqu’ici, List, Set, Map et Tuple, sont toutes évaluées avec empressement (c’est-à-dire immédiatement). Bien sûr, vous savez maintenant que les flux Java 8 sont évalués à la demande (c’est-à-dire, paresseusement). Vous avez vu au chapitre 5 qu’à cause de cette propriété, les flux peuvent représenter une séquence infinie sans déborder la mémoire.

Scala fournit une structure de données évaluée paresseusement correspondante appelée Stream aussi! Mais les flux dans Scala fournissent plus de fonctionnalités que ceux de Java. Les flux dans Scala mémorisent les valeurs qui ont été calculées afin qu’on puisse accéder aux éléments précédents. En outre, les flux sont indexés de sorte que les éléments puissent être accessible par un index, tout comme une liste. Notez que le compromis pour ces propriétés supplémentaires est que les Streams sont moins efficaces en termes de mémoire par rapport aux flux de Java 8, car le fait de pouvoir référencer les éléments précédents signifie que les éléments doivent être « mémorisés ».

Option

Une autre structure de données que vous connaissez est Option. C’est la version Scala de Java 8’s Optional, dont nous avons parlé au chapitre 10. Nous avons fait valoir que vous devriez utiliser Optional pour concevoir de meilleures API, dans lesquelles il suffit de lire la signature d’une méthode pour savoir si elle peut avoir ou non une valeur. Il devrait être utilisé à la place de null lorsque cela est possible pour empêcher les exceptions de pointeur nul.

Vous avez vu au chapitre 10 que vous pouviez utiliser Option pour retourner le nom de l’assurance d’une personne si son âge est supérieur à un âge minimum, comme suit:

Dans Scala, vous pouvez utiliser Option d’une manière similaire à Optional:

Vous pouvez reconnaître les mêmes noms de structure et de méthode en dehors de getOrElse, qui est l’équivalent de orElse dans Java 8. Vous voyez, tout au long de ce livre, vous avez appris de nouveaux concepts qui peuvent être directement appliqués à d’autres langages de programmation! Malheureusement, null existe également dans Scala pour des raisons de compatibilité Java et son utilisation est fortement déconseillée.



Dans le code précédent, vous avez écrit _.getCar (sans parenthèses) au lieu de _.getCar () (avec des parenthèses). Dans Scala, les parenthèses ne sont pas obligatoires lors de l’appel d’une méthode qui ne prend aucun paramètre.



15.2. Les fonctions

Les fonctions Scala peuvent être vues comme une séquence d’instructions qui sont regroupées pour exécuter une tâche. Elles sont utiles pour un comportement abstrait et sont la pierre angulaire de la programmation fonctionnelle.

En Java, vous connaissez les méthodes: fonctions associées à une classe. Vous avez également vu des expressions lambda, qui peuvent être considérées comme des fonctions anonymes. Scala propose un ensemble de fonctions plus complet que Java, que nous examinons dans cette section. Scala fournit les éléments suivants:

  • Types de fonctions, sucre syntaxique pour représenter l’idée des descripteurs de fonction Java (c’est-à-dire une notation pour représenter la signature de la méthode abstraite déclarée dans une interface fonctionnelle), que nous avons décrite au chapitre 3
  • Les fonctions anonymes qui n’ont pas les restrictions d’écriture sur les variables non locales que les expressions lambda de Java ont
  • Prise en charge du Currying, ce qui implique de décomposer une fonction qui prend plusieurs arguments en une série de fonctions qui prennent en compte seulement une partie des arguments

15.2.1. Fonctions de première classe à Scala

Les fonctions dans Scala sont des valeurs de première classe. Cela signifie qu’ils peuvent être transmis en tant que paramètres, retournés en conséquence, et stockés dans des variables, tout comme d’autres valeurs telles qu’un entier ou une String. Comme nous vous l’avons montré dans les chapitres précédents, les références de méthode et les expressions lambda dans Java 8 peuvent également être considérées comme des fonctions de première classe.

Regardons un exemple de la façon dont les fonctions de première classe fonctionnent dans Scala. Disons que vous avez une liste de String représentant les tweets que les gens vous envoient. Vous souhaitez filtrer cette liste avec différents critères, par exemple, les tweets qui mentionnent le mot Java ou les tweets de courte durée. Vous pouvez représenter ces deux critères comme des prédicats (fonctions renvoyant un booléen):

Dans Scala, vous pouvez transmettre ces méthodes directement au filtre intégré de la manière suivante (tout comme vous pouvez les passer en utilisant les références de méthode en Java):

Inspectons maintenant la signature du filtre de méthode intégré:

Vous vous demandez peut-être quel est le type de paramètre p (ici (T) => Booléen), car en Java vous attendez une interface fonctionnelle! C’est une nouvelle syntaxe qui n’est pas disponible en Java. Il décrit un type de fonction. Ici, il représente une fonction qui prend un objet de type T et renvoie un booléen. En Java, ceci est codé comme un Predicat <T> ou une function <T, booléenne>. C’est exactement la même signature que les méthodes isJavaentioned et isShortTweet donc vous pouvez les passer en argument à filter. Les concepteurs de langage Java 8 ont décidé de ne pas introduire une syntaxe similaire pour les types de fonctions afin de garder le langage cohérent avec les versions précédentes. (Introduire trop de nouvelle syntaxe dans une nouvelle version du langage est considéré comme trop de surcharge cognitive supplémentaire.)

15.2.2. Fonctions anonymes et Closures

Scala soutient également le concept de fonctions anonymes. Ils ont une syntaxe similaire aux expressions lambda. Dans l’exemple suivant, vous pouvez affecter à une variable nommée isLongTweet, une fonction anonyme qui vérifie si un tweet donné est long:

 

Maintenant, en Java, une expression lambda vous permet de créer une instance d’une interface fonctionnelle. Scala a un mécanisme similaire. Le code précédent est du sucre syntaxique pour déclarer une classe anonyme de type scala.Function1 (une fonction d’un paramètre), qui fournit l’implémentation de la méthode apply:

Comme la variable isLongTweet contient un objet de type Function1, vous pouvez appeler la méthode apply, qui peut être considérée comme l’appel de la fonction:

Comme dans Java, vous pouvez faire ce qui suit:

Pour utiliser les expressions lambda, Java fournit plusieurs interfaces fonctionnelles intégrées telles que Predicate, Function et Consumer. Scala fournit des traits (vous pouvez considérer les traits comme des interfaces pour l’instant jusqu’à ce que nous les décrivions dans la section suivante) pour obtenir la même chose: Function0 (une fonction avec 0 paramètres et un résultat de retour) jusqu’à Function22 (une fonction avec 22 paramètres) , qui définissent toutes, la méthode apply.

Un autre truc sympa dans Scala est que vous pouvez appeler la méthode apply en utilisant du sucre syntaxique qui ressemble plus à un appel de fonction:

Le compilateur convertit automatiquement un appel f(a) en f.apply(a) et, plus généralement, un appel f (a1, …, an) en f.apply (a1, …, an), si f est un objet qui supporte la méthode apply (notez que apply peut avoir n’importe quel nombre d’arguments).

Closures

Dans le chapitre 3, nous avons commenté si les expressions lambda dans Java constituaient des fermetures. Pour vous rafraichir, une fermeture est une instance d’une fonction qui peut référencer des variables non locales de cette fonction sans restrictions. Mais les expressions lambda dans Java 8 ont une restriction: elles ne peuvent pas modifier le contenu des variables locales d’une méthode dans laquelle le lambda est défini. Ces variables doivent être implicitement finales. Cela aide à penser que lambda se rapproche des valeurs plutôt que des variables.

En revanche, les fonctions anonymes dans Scala peuvent capturer les variables elles-mêmes, pas les valeurs auxquelles les variables se réfèrent actuellement. Par exemple, ce qui suit est possible dans Scala:

Mais en Java, le résultat suivant entraînera une erreur de compilation car count est implicitement forcé d’être final:

Nous avons soutenu dans les chapitres 7, 12 et 13 que vous devriez éviter les mutations lorsque cela est possible pour rendre vos programmes plus faciles à maintenir et à paralléliser, donc utilisez cette fonctionnalité uniquement lorsque cela est strictement nécessaire.

15.2.3. Currying

Dans le chapitre 13, nous avons décrit une technique appelée currying: où une fonction f de deux arguments (x et y, disons) est vue à la place comme une fonction g d’un argument, qui retourne une fonction aussi d’un argument. Cette définition peut être généralisée aux fonctions avec plusieurs arguments, produisant plusieurs fonctions d’un argument. En d’autres termes, vous pouvez décomposer une fonction qui prend plusieurs arguments en une série de fonctions prenant une sous-partie des arguments. Scala fournit une construction pour vous permettre d’utiliser facilement une fonction existante.

Pour comprendre ce que Scala apporte, voyons d’abord un exemple en Java. Vous pouvez définir une méthode simple pour multiplier deux entiers:

Mais cette définition nécessite que tous les arguments lui soient transmis. Vous pouvez décomposer manuellement la méthode multiplier en lui faisant retourner une autre fonction:

La fonction retournée par multiplyCurry capture la valeur de x et la multiplie par son argument y, renvoyant un entier. Cela signifie que vous pouvez utiliser multiplyCurry comme suit dans une carte pour multiplier chaque élément par 2:

Cela produira le résultat 2, 6, 10, 14. Cela fonctionne car la map attend une fonction comme argument et multiplyCurry renvoie une fonction!

Maintenant, il est un peu fastidieux en Java de diviser manuellement une fonction pour créer une forme de currying (surtout si la fonction a plusieurs arguments). Scala a une syntaxe spéciale pour le faire automatiquement. Vous pouvez définir la méthode de multiplication normale comme suit:

Et voici sa forme en currying:

En utilisant la syntaxe (x: Int) (y: Int), la méthode multiplyCurry prend deux listes d’arguments d’un paramètre Int. En revanche, multiplier prend une liste de deux paramètres Int. Que se passe-t-il lorsque vous appelez MultiplyCurry? La première invocation de multiplyCurry avec un seul Int(le paramètre x), multiplyCurry (2), renvoie une autre fonction qui prend un paramètre y et le multiplie avec la valeur capturée de x (ici la valeur 2). Nous disons que cette fonction est partiellement appliquée comme expliqué dans la section 14.1.2, car tous les arguments ne sont pas fournis. La deuxième invocation multiplie x et y. Cela signifie que vous pouvez stocker la première invocation à multiplierCurry dans une variable et la réutiliser:

En comparaison avec Java, dans Scala vous n’avez pas besoin de fournir manuellement la forme curry d’une fonction comme cela est fait ici. Scala fournit une syntaxe pratique de définition de fonction pour indiquer qu’une fonction a plusieurs listes d’arguments en currying.

15.3. Classes et traits

Nous examinons maintenant comment les classes et les interfaces de Java se comparent à Scala. Ces deux constructions sont primordiales pour concevoir des applications. Vous verrez que les classes et les interfaces de Scala peuvent offrir plus de flexibilité que ce que celles de Java.

15.3.1. Moins de verbosité avec les classes Scala

Parce que Scala est également un langage orienté objet complet, vous pouvez créer des classes et les instancier pour produire des objets. À la forme la plus basique, la syntaxe pour déclarer et instancier des classes est similaire à Java. Par exemple, voici comment déclarer une classe Hello:

Getters et setters

Cela devient plus intéressant une fois que vous avez une classe avec des champs. Avez-vous déjà rencontré une classe Java qui définit uniquement une liste de champs, et vous avez dû déclarer une longue liste de getters, de setters et de constructeurs appropriés? Quelle douleur! En outre, vous verrez souvent des tests pour l’implémentation de chaque méthode. Une grande quantité de code est généralement consacrée à ces classes dans les applications Enterprise Java. Considérez cette classe d’étudiant simple:

Vous devez définir manuellement le constructeur qui initialise tous les champs, deux getters et deux setters. Une classe simple a maintenant plus de 20 lignes de code! Plusieurs IDE et outils peuvent vous aider à générer ce code, mais votre code doit encore gérer une grande quantité de code supplémentaire qui n’est pas très utile par rapport à la vraie logique métier.

Dans Scala, les constructeurs, les getters et les setters peuvent être générés implicitement, ce qui génère du code avec moins de verbosité:

15.3.2. Traits en Scala vs. interfaces en Java 8

Scala a une autre caractéristique utile pour l’abstraction appelée traits. Ils sont le remplacement de Scala pour les interfaces de Java. Un trait peut définir à la fois des méthodes abstraites et des méthodes avec une implémentation par défaut. Les traits peuvent également être hérités plusieurs fois, tout comme les interfaces Java. Vous pouvez donc les voir comme similaires aux interfaces de Java 8 qui prennent en charge les méthodes par défaut. Les traits peuvent également contenir des champs comme une classe abstraite, que les interfaces Java 8 ne prennent pas en charge. Est-ce que les traits sont comme les classes abstraites? Non, car les traits peuvent être hérités plusieurs fois, contrairement aux classes abstraites. Java a toujours eu plusieurs héritages de types car une classe peut implémenter plusieurs interfaces. Maintenant, Java 8, à travers les méthodes par défaut, introduit l’héritage multiple des comportements mais ne permet toujours pas l’héritage multiple de l’état, ce qui est autorisé par les traits de Scala.

Pour montrer un exemple de ce à quoi ressemble un trait dans Scala, définissons un trait appelé Sized qui contient un champ modifiable appelé size et une méthode appelée isEmpty avec une implémentation par défaut:

Vous pouvez maintenant le composer au moment de la déclaration avec une classe, ici une classe vide qui a toujours la taille 0:

Fait intéressant, par rapport aux interfaces Java, les traits peuvent également être composés au moment de l’instanciation de l’objet (mais il s’agit toujours d’une opération de compilation). Par exemple, vous pouvez créer une classe Box et décider qu’une instance spécifique doit prendre en charge les opérations définies par le trait:

Que se passe-t-il si plusieurs caractères sont hérités déclarant des méthodes avec les mêmes signatures ou des champs avec les mêmes noms? Scala fournit des règles de restriction similaires à celles que vous avez vues avec les méthodes par défaut dans le chapitre 9.

15.4. Résumé

Voici les concepts clés que vous devriez retenir de ce chapitre:

  • Java 8 et Scala combinent des fonctionnalités de programmation orientées objet et fonctionnelles dans un langage de programmation; Les deux fonctionnent sur la JVM et peuvent dans une large mesure interopérer.
  • Scala prend en charge les abstractions de collection similaires à Java 8-List, Set, Map, Stream, Option, mais prend également en charge les tuples.
  • Scala fournit des fonctionnalités plus riches autour des fonctions que Java 8: les types de fonctions, les fermetures qui n’ont aucune restriction sur l’accès aux variables locales, et les formes de curry intégrées.
    Les classes dans Scala peuvent fournir des constructeurs implicites, des getters et des setters.
  • Scala prend en charge les traits: les interfaces peuvent inclure à la fois des champs et des méthodes par défaut.