Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Un aperçu du langage

Introduction

Dans ce chapitre, la totalité des exemples de code présentés peut se retrouver dans le fichier chapitre2.sc du projet scala-book. Comme il s’agit de morceaux de code non reliés, ils sont rassemblés dans un fichier de script.

Pour rappel, les exemples de code sont séparés en deux parties :

  • le code lui-même ;

  • le retour de la console REPL qui commence avec //.

Si on lance le script Scala, la deuxième partie ne s’affiche pas, mais on peut copier-coller le code dans la console REPL pour l’afficher.

Variables

1. Définition

En Scala, il existe deux types de variables :

  • val pour définir des variables immuables, comme les variables finales en Java.

  • var pour définir des variables mutables.

La plupart des variables en Scala sont définies avec le mot-clé val ; ainsi, lorsqu’on parle de variable, on parle de variable immuable. Le mot-clé var est utilisé ponctuellement pour des occasions spécifiques et on parle de variable mutable.

Prenons comme exemple la variable jour.

val jour = "Lundi" 
// jour: String = Lundi 

Si on essaie de modifier sa valeur, on obtient un message d’erreur.

jour = "Mardi" 
// Error: reassignment to val 

Avec une variable mutable jourMutable, cette modification est possible.

var jourMutable = "Lundi" 
// jourMutable: String = Lundi 
 
jourMutable = "Mardi" 
// jourMutable: String = Mardi 

2. Variable lazy

Lorsqu’une variable est définie, son contenu est évalué, comme on l’a vu dans la partie précédente. On peut empêcher ce comportement en utilisant le mot-clé lazy val au lieu de val. Ainsi, elle ne sera évaluée que lorsqu’elle sera appelée.

lazy val mot = "Aujourd'hui" 
// mot: String = <lazy> 
 
mot 
// res0: String = Aujourd'hui 

Cela peut être utile dans le cas où on veut définir une variable et l’utiliser uniquement dans un cas précis.

Prenons par exemple une variable lazy qui contient les millisecondes actuelles. On veut créer une chaîne de caractères qui donne la date actuelle selon la langue du système.

lazy val dateCourante = System.currentTimeMillis() 
// dateCourante: Long = <lazy> 
 
val langueLocale = Locale.getDefault.getLanguage 
// langueLocale: String = en 
 
val dateActuelle = if (langueLocale == "fr") { 
  "Temps actuel : " + dateCourante 
} else if (langueLocale == "en") { 
  "Current time: " + dateCourante 
} else { 
  "Langage inconnu" 
} 
// dateActuelle: String = Current time: 1622375853667 

Avec cette méthode, la variable dateCourante n’est évaluée qu’au moment où...

Entrée/Sortie

1. Écrire dans la console

Toutes les méthodes concernant l’écriture dans la console se trouvent dans la classe System présente dans le paquet java.lang.

Pour écrire dans la console, on utilise la méthode print pour imprimer ou println pour ajouter en plus un saut à la ligne à la fin du texte.

Par défaut, ces méthodes écrivent dans la console standard et correspondent aux méthodes présentes dans le paquet out. Pour écrire dans la console d’erreur, on utilise les méthodes du paquet err.

System.out.println("Message d'information") 
// Message d'information 
 
System.err.println("Message d'erreur") 
// Message d'erreur 

On peut mettre en forme le texte de sortie grâce aux éléments présents dans la classe Console :

  • BOLD pour rendre le texte gras.

  • UNDERLINED pour souligner le texte.

  • BLUE pour colorer le texte en bleu.

  • BLUE_B pour colorer le fond en bleu.

Les couleurs suivantes sont disponibles pour colorer le texte et le fond :

  • BLACK

  • RED

  • GREEN

  • YELLOW

  • BLUE

  • MAGENTA

  • CYAN

  • WHITE

Une fois un élément de style appliqué, il s’ajoute aux éléments précédents et est actif jusqu’à l’appel de l’élément RESET.

images/02EP03.png

2. Lire depuis la console

Pour lire depuis la console, on utilise...

Les structures de contrôle

1. Structure if/else

Comme en Java, on définit un bloc if / else en décrivant la première condition dans une clause if, les suivantes dans des clauses else if et la dernière dans une clause else.

if (bonneReponse && !mauvaiseReponse) { 
 "GAGNÉ" 
} else if (bonneReponse && mauvaiseReponse) { 
 "PRESQUE" 
} else { 
 "PERDU" 
} 
// res213: String = GAGNÉ 

Contrairement à Java, le bloc if/else renvoie toujours une valeur et on peut donc assigner son résultat dans une variable. De plus, dans le cas où le bloc ne comprend qu’une seule ligne, on peut supprimer les accolades pour réduire le bloc pour plus de lisibilité.

val resultat = if (bonneReponse) "GAGNÉ" else "PERDU" 
// resultat: String = GAGNÉ 

Cette syntaxe est semblable à l’opération ternaire disponible en Java :

(bonneReponse) ? "GAGNÉ" : "PERDU"; 

2. Structure for

En Scala, il existe deux types de boucles for : celle qui ne renvoie pas de valeur, comme en Java, et celle qui renvoie une valeur, utilisable pour assigner son résultat dans une variable.

a. Boucle for classique

La première boucle for se présente de cette façon : for (element <- iterateur) resultatiterateur correspond à une liste ou une plage de nombres. Le résultat peut se présenter sur une seule ligne ou un bloc de code.

Prenons comme...

Méthodes

1. Définition

Une méthode est une expression définie dans une classe ou un objet et qui prend en entrée des arguments et renvoie un résultat. Dans notre exemple, toutes les méthodes seront définies dans le script.

Pour définir une méthode, on utilise la syntaxe suivante :  def nom(t: T) = ???.

La partie gauche correspond au nom et aux arguments de la méthode et la partie droite aux opérations effectuées sur ces paramètres.

Une méthode peut prendre aucun, un ou plusieurs arguments.

def unArg(i: Int) = i * 1.2 
// unArg: (i: Int)Double 
 
def deuxArg(i: Int, j: Int): Double = i * j 
// deuxArg: (i: Int, j: Int)Double 
 
def sansArg() = () 
// sansArg: ()Unit 

a. Type de retour d’une méthode

Toutes ces méthodes possèdent un type de retour, même la méthode qui ne fait rien. Ce type de retour, Unit, correspond au void en Java. On peut spécifier ce type de retour, auquel cas il prévaudra sur le type inféré par le compilateur, comme dans le cas de notre méthode deuxArg qui force la conversion du type Int vers le type Double.

Dans le cas de la méthode sans argument, on peut enlever les parenthèses pour plus de lisibilité.

Contrairement au Java, on n’a pas besoin d’utiliser le mot return pour spécifier le résultat....

Classes

1. Création d’une classe

Pour définir une classe, on utilise le mot-clé class suivi du nom de cette classe et de ses éventuels paramètres, comme en Java.

Prenons comme exemple la classe Vetement qui prend comme arguments un nom.

class Vetement(nom: String) 
// defined class Vetement 

Pour définir une instance de cette classe, on utilise le mot-clé new suivi du nom de la classe et des arguments.

val short = new Vetement("short") 
// short: Vetement = Vetement@63ed4f58 

Dans une classe, on peut définir des variables supplémentaires, des méthodes, fonctions ou autres constructeurs comme on le verra dans les sections suivantes. 

2. Accès aux champs

En Java, pour accéder aux paramètres de la classe, on doit créer des accesseurs.

En Scala, on peut également le faire mais il existe une méthode plus élégante. On ajoute le mot-clé var ou val, selon si on souhaite modifier ou pas ce paramètre, devant le nom de ce dernier. Pour accéder à un paramètre x d’un objet a, il suffit alors d’utiliser x.a.

Prenons comme exemple la classe Vetements qui prend comme arguments un nom immuable et un nombre mutable.

class Vetements(val nom: String, var nombre: Int) 
// defined class Vetements 

Ainsi, pour une instance de la classe Vetements, on peut accéder à son nom et son nombre.

val shorts = new Vetements("short", 3) 
// shorts: Vetements = Vetements@57e0f329 
 
shorts.nom 
// res231: String = short 
 
shorts.nombre 
// res232: Int = 5 

On peut également modifier son paramètre nombre mais on obtient une erreur à la compilation si on essaye de modifier son nom.

shorts.nom = "SHORT" 
// reassignment to val 
 
shorts.nombre += 2 ...

Héritage

1. Définition

Comme en Java, on peut étendre une classe unique et hériter de ses variables et méthodes. Pour ce faire, il suffit d’ajouter le mot-clé extends suivi de la classe parent et de ses arguments.

Prenons comme exemple une classe Appartement qui étend la classe Maison définie plus tôt. Un Appartement a toujours une chambre et prend comme argument un lieu.

class Appartement(lieu: String) extends Maison(1, lieu) 
// defined class Appartement 

Une instance de cette classe aura accès au champ lieu défini comme argument de la classe enfant, mais aussi à tous les champs définis dans la classe parent, que ce soit le nombre de chambres ou la description.

val appartement = new Appartement("Lyon") 
// appartement: Appartement = Appartement@75f3e7b1 
 
appartement.lieu 
// res249: String = Lyon 
 
appartement.description 
// res250: String = Maison à Lyon avec 1 chambres 
 
appartement.chambres 
// res251: Int = 1 

L’objet obtenu est une instance à la fois de la classe Appartement et de la classe Maison.

appartement.isInstanceOf[Appartement] 
// res252: Boolean = true 
 
appartement.isInstanceOf[Maison] 
// res253: Boolean = true 

On peut créer un objet Maison à partir de cet appartement en forçant son type lors de l’instanciation, mais l’inverse n’est pas possible. Lorsqu’on essaye de typer un objet Maison en tant qu’objet Appartement, une erreur se produit à la compilation.

val appartementMaison: Maison = appartement 
// appartementMaison: Maison = Appartement@42f51320 
 
val maisonAppartement:...

Objets singletons

1. Définition

Un objet singleton est une valeur qu’on définit de la même façon qu’une classe avec le mot-clé object. C’est à la fois une classe et une instance de cette même classe qui est créée lors du premier appel.

Cela permet de définir des méthodes ou des constantes accessibles depuis tout le projet sans devoir créer une instance de classe ou étendre une classe.

Prenons comme exemple l’objet Compteur qui contient :

  • une variable nom de type String,

  • une variable privée mutable total de type Int,

  • une méthode decompte qui récupère la valeur de la variable total,

  • une méthode ajouter qui incrémente la variable total.

object Compteur { 
 val nom = "COMPTEUR" 
 private var total = 0 
 def decompte = total 
 def ajouter = total += 1 
} 

Depuis une autre classe ou un autre objet, on peut appeler les méthodes et récupérer les variables accessibles de cet objet en indiquant son nom devant les éléments appelés.

Dans notre exemple, on peut incrémenter le compteur avec la méthode ajouter, récupérer le total avec la méthode decompte et la variable nom mais pas la variable total qui est privée.

Compteur.ajouter 
Compteur.decompte 
// res259: Int = 1 
 
Compteur.nom 
// res260: String = COMPTEUR 
 
Compteur.total 
// Error: variable total in object Compteur cannot be accessed in 
object Compteur 

Si l’objet se trouve dans un paquet différent, il suffit d’importer l’objet pour pouvoir l’utiliser. Si on veut utiliser un des éléments sans spécifier son nom, il suffit d’utiliser le mot-clé ._ pour importer tout le contenu de l’objet.

Prenons comme exemple l’objet Random. Il permet de générer...

Exemple complet

Dans cet exemple final, nous allons créer une application qui pose des questions en bleu, lit des chaînes de caractères en entrée et imprime des résultats en vert ou rouge selon la réussite. Cet exemple est disponible dans la classe ChapitreDeux présent dans le paquet eni.

Tout d’abord, on demande à l’utilisateur son âge avec la méthode readInt.

print(s"${BLUE}Quel est votre âge ? $RESET") 
val age = readInt() 

Ensuite, on demande le nombre de dates que l’utilisateur souhaite renseigner avec la méthode readInt.

print(s"${BLUE}Combien de dates voulez-vous renseigner ? $RESET") 
val nombre = readInt() 

Le but est que l’utilisateur renseigne des dates au format dd/MM/yyyy et de comparer l’âge obtenu avec l’âge de l’utilisateur. Si un des âges correspond à l’âge de l’utilisateur, on indique le numéro de la personne en vert. Sinon, on affiche une phrase d’échec en rouge. On donnera également à la fin du programme l’âge moyen des personnes avec un chiffre significatif.

Pour cela, on crée un formateur pour transformer la chaîne de caractères en élément de type LocalDate et on instancie un élément correspondant à la LocalDate actuelle pour avoir un point...