
Passer de Java à Scala – Partie 2 – Tableaux et collections
Dans cette seconde partie du tutoriel « Passer de Java à Scala », nous allons voir comment utiliser les différentes collections de Scala que sont les Array, les Map, les Set, les List et les Tuples.
Si vous ne l’avez pas déjà fait, je vous invite à consulter la 1ere partie de la série présentant le shell scala ainsi que l’article présentant globalement le passage de Java à Scala.
Retrouvez le code source des exemples ici : https://github.com/alexandrelanglais/scala-tutorial-2
Retrouvez tous les articles de la série « Passer de Java à Scala »
Collections en Scala
Les tableaux
On peut définir un tableau de plusieurs façon en Scala. Par exemple :
val greetStrings = new Array[String](3) greetStrings(0) = "Hello" greetStrings(1) = ", " greetStrings(2) = "world!\n"
Ici on assigne à la variable immutable greetStrings un nouveau tableau de String de taille 3. On assigne ensuite une par une les valeurs de ce dernier.
Il est possible d’être encore plus explicite et de spécifier le type de la variable à la déclaration :
val greetStrings:Array[String] = new Array[String](3)
Mais vu la détermination automatique de type dont sait faire preuve Scala, cette dernière expression n’est pas nécessaire.
Au vu de la note ci-dessus, écrire
greetStrings(0) = "Hello"
Est équivalent à écrire
greetStrings.update(0, "Hello")
Scala simplifie considérablement le travail du développeur de par sa capacité à substituer la concision à la verbosité.
Il est possible d’être plus concis encore pour écrire le code d’initialisation du tableau précédent :
val numNames = Array("zero", "one", "two") for (i <- 0 to 2) println(numNames(i))
Une fois de plus, on pourrait spécifier le type du tableau via numName:Array[String].
Ceci est d’ailleurs nécessaire si l’on souhaite s’assurer que le tableau ne contienne que des String.
En effet, le programme suivant :
// Tableau de n'importe quoi val anything = Array(1, "Hello", java.math.BigDecimal.ZERO) for (i <- 0 to 2) println(anything(i).getClass)
Générera la sortie :
class java.lang.Integer class java.lang.String class java.math.BigDecimal
Il s’agit en réalité d’un tableau de type Array[Any], Any en Scala étant l’équivalent du Object de Java.
Pour forcer la paramétrisation du tableau, il faut écrire :
// Erreur de compilation : Array[Any] ne correspond pas à Array[String] val neCompilePas:Array[String] = Array(1, "Hello", java.math.BigDecimal.ZERO)
Utilisation des listes
L’un des principes de la programmation fonctionnelle est que les méthodes ne devraient pas avoir d’effet de bord : elles devraient uniquement servir à retourner une valeur.
En programmation fonctionnelle, on privilégie les val aux var car elles sont immutables. De la même manière, en Scala on privilégie les Collections (List, Map, Set) car par défaut, elles sont immutables (contrairement aux Array).
L’initialisation d’une liste se fait comme pour un tableau :
// Creation et initialisation val oneTwoThree = List(1, 2, 3)
Immutabilité des listes
Cette liste est immutable. Il n’y a pas de méthode update() et on ne peut écrire
oneTwoThree(0) = 4 // erreur de compilation
Si l’on veut rajouter des éléments à cette liste, on doit en créer une nouvelle et utiliser l’opérateur de concaténation ::: :
// Concaténation de listes val fourFiveSix = List(4, 5, 6) val concatenation = oneTwoThree ::: fourFiveSix concatenation.foreach(s => { print(s); print(" ") })
On obtiendra en sortie :
1 2 3 4 5 6
Les listes oneTwoThree et fourFiveSix n’ont pas changé. On a créé la nouvelle liste concatenation à partir des 2 premières.
L’opérateur le plus commun avec les listes est le double deux-points :: aussi appelé cons. Il permet d’ajouter un élément en début de liste :
val zeroOneTwoThree = 0 :: oneTwoThree zeroOneTwoThree.foreach(s => { print(s); print(" ") })
L’opérateur :: s’applique à la liste. Il est particulier car au lieu d’être suffixé à la liste via un point . comme on en a l’habitude en Java, celui-ci est préfixé.
Nil
Le mot Nil représente une liste vide. Au lieu d’écrire
val l = List()
On peut simplement écrire
val l = Nil
Pour voir le chainage de l’opérateur cons :: on peut définir une liste en utilisant Nil de la manière suivante :
val nilList = 1 :: 2 :: 3 :: Nil
En lisant de droite à gauche, la list Nil effectue l’opération :: 3 retournant une liste, sur laquelle on peut enchainer les ::.
Pas de méthode pour append ?
Bien qu’il soit possible de faire un append sur une liste via l’opérateur += cette pratique est déconseillée car peu performante :
// Ajouter un élément en fin de liste val appended = nilList :+ 4
Il vaut mieux faire un reverse puis ajouter l’élément en premier, puis reverse de nouveau :
// Meilleur ajout d'un élément en fin de liste val appendedNicely = (4 :: nilList.reverse).reverse
Fonctions de List
Retrouvez toutes les fonctions de List sur la documentation officielle ici : https://www.scala-lang.org/api/current/scala/collection/immutable/List.html
Utilisation des tuples
Les tuples sont des listes non paramétrisées. Ils sont surtout utiles lorsque l’on souhaite stocker des données de types différents sans avoir à créer de classe bean les englobant :
// Utilisation d'un tuple val carte = (10, "pique") println(carte._1) println(carte._2)
Affichera
10 pique
Sets et Maps
Sets
Scala propose un trait pour les Set. Un trait est semblable à une interface en Scala (nous y reviendrons dans un prochain article).
Les collections sont séparées en 2 catégories : les mutables (modifiables) et les immutables (non-modifiables) comme exposé dans le graphique ci-dessous :
Sans autre spécification, la classe utilisée par défaut pour les Set est celle en bas à gauche, un HashSet immutable.
Un Set propose une méthode / opérateur + pour y ajouter des éléments. Cependant, le Set doit être mutable pour que cela fonctionne sur un set déclaré en val :
// Utilisation d'un set immutable var kaSet = Set("Audio", "Video") kaSet += "Blueray" println(kaSet.contains("Blueray")) // déclaré en val val immutableSet = Set("Audio", "Video") immutableSet += "Blueray" // erreur de compilation : réassignation de variable
Pour utiliser un Set mutable, il faut le spécifier en important la classe en haut du fichier ou en le déclarant explicitement :
import scala.collection.mutable.Set object ScalaMutableSet extends App { // mutable set val mutableSet = Set("Audio", "Video") mutableSet += "Blueray" // compile }
Sur une collection immutable, l’opérateur + créé un nouvel objet. On peut par exemple écrire
import scala.collection.immutable.HashSet object ScalaImmutableSet extends App { val hashSet = HashSet("Tomates", "Concombres") println(hashSet + "Patates de terre") println(hashSet) }
L’output sera
Set(Patates de terre, Tomates, Concombres) Set(Tomates, Concombres)
(Oui je sais on dit pommes de terre ou patates mais j’aime bien mélanger les 2 😉 )
Remarquez comme l’opérateur + a placé l’élément en début de Set pour des raisons de performances.
Maps
Comme pour les Set, Scala fournit des traits de base pour les maps :
Voyons comment on peut utiliser une mutable map :
import scala.collection.mutable.Map object ScalaMutableMap extends App { // mutable map val mutableMap = Map[Int, String]() mutableMap += (1 -> "Ceci est") mutableMap += (2 -> "une") mutableMap += (3 -> "mutable map") println(mutableMap(3)) }
Le résultat :
mutable map
L’opérateur += de Map permet d’ajouter des éléments à celle-ci. Elle prend en paramètre une paire clé -> valeur dont les types correspondent à la déclaration de la Map[Int, String].
Enfin on note que le .get est implicite pour mutableMap(3).
Conclusion
Voilà qui clôt ce chapitre sur les Collections en Scala. Dans la prochaine partie nous parlerons des classes et des objets.