août
2008
Dans ce second volet de l’introduction à Scala, je vais présenter comment définir et utiliser les classes et les objets avec Scala.
1. Classes
1.1. Définition d’une Classe
Je vais commencer par les classes. Scala ne joue pas aux fantaisies ici, et une classe est toujours une structure regroupant champs et traitements.
Voici la déclaration minimale d’une classe en Scala
A l’inverse de Java, une classe est par défaut publique, donc plus besoin d’ajouter public à la déclaration.
D’ailleurs, le mot public n’existe même pas pour Scala.
Là, on peut se poser la question de ou est passé le modificateur de visibilité par défaut de Java, qui limite l’accès à un champ/classe au package dans lequel il/elle est défini ?
Scala va beaucoup plus loin (encore une fois) que Java en offrant un contrôle très précis de qui a le droit d’accéder à tel ou tel champ/classe, mais ce n’est pas dans le cadre de ce billet.
1.2. Définition des champs
Ajoutons maintenant quelques champs à cette classe, ce qui donne:
La déclaration d’un champ se présente comme suit:
[modificateur de visibilité] (var|val) nom [:Type] [= Valeur]
les parties entre [ et ] sont optionnelles, et (x|y) indique un choix exclusif entre deux options x et y.
var indique qu’un champ est muable tandis que val indique qu’un champ est immuable.
Une note importante s’impose: si un champ n’est pas initialisé avec une valeur lors de sa déclaration, il est considéré par Scala comme champ abstrait.
Vous êtes déjà habitués aux méthodes et classes abstraites dans Java. Scala va encore plus loin en étendant cette notion au champs aussi.
Ainsi, il est nécessaire d’assigner une valeur à un champ lors de sa déclaration si on ne veut pas qu’il soit considéré comme abstrait.
Dans le cas où on n’a pas de valeur précise à assigner comme valeur par défaut à un champ, on peut utiliser le ‘wild card‘ _ de Scala comme valeur, comme ici:
Le ‘_’ serait traduit par le compilateur en la valeur adapté au type du champ, 0 pour les Int, « » pour les chaînes, etc.
Optimisons encore la chose en tirant profit du puissant système d’inférence de types de Scala, qui nous permet de ne pas avoir à declarer le type d’un champ du moment que ce même type peut être déterminé depuis la valeur d’initialisation:
Tout comme pour les classes, les champs dans Scala sont publics par défaut.
Voici maintenant comment on peut instancier une telle classe et l’utiliser:
Là, je vois déjà les puristes OO en désarroi face à cette absence d’encapsulation: accès direct aux champs ?? !!
Rassurez vous, même si ça en a tout l’air, ‘under the hoods‘, Scala alloue un espace mémoire pour un champ inaccessible au programmeur, et génère automatiquement l’équivalent d’un getter et d’un setter Java et ce sont eux qui sont invoqués lors de la lecture et écriture (plus de détails dans la suite).
1.3. Constructeurs
a. Constructeur principal
Je vais maintenant montrer une autre fonctionnalité puissante de Scala qui s’adapte bien à ce genre d’objets (conteneurs de données): les constructeurs.
Les constructeurs d’une classe dans Scala sont un peu différents de ceux en Java, et sont considérés des ‘first class citizens‘.
Voici comment on définit le constructeur principal d’une classe Scala:
Yep, la classe tient désormais sur une seule ligne
Ce bout de code définit à la fois les deux champs name et age ainsi qu’un constructeur qui prend les valeurs de ces deux champs comme paramètres.
Voici comment instancier une telle classe:
Qui comme vous le remarquez est similaire à ce qu’on fait en Java.
b. Constructeurs auxiliaires
Maintenant, la question qui se pose, est comment déclarer, tout comme dans Java, d’autre constructeurs de signatures différentes ?
C’est tout à fait logique vu comment est défini le constructeur principal.
Scala impose la limite suivante: on peut définir d’autres constructeurs, mais ils doivent obligatoirement invoquer directement ou indirectement le constructeur principal.
Voici comment déclarer d’autres constructeurs (secondaires ou auxiliaires):
Et voici comment invoquer ces nouveaux constructeurs
Les constructeurs secondaires peuvent très bien effectuer d’autres traitements, comme dans ce cas par exemple:
Mais la règle d’invocation du constructeur principal tient toujours. Ici, ça se fait indirectement.
1.4. Getters et Setters
La question suivante est comment spécifier par exemple son propre getter et setter pour un champ ainsi défini ?
Voici comment ça se fait:
J’ai d’abord ajouté private à la déclaration de age dans le constructeur (en le renommant en a du même coup) et défini deux méthodes (via le mot clé def):
- age: qui ne fait que retourner le champ privé a en lui ajoutant la chaine » ans », d’où le type de retour en String. Remarque que j’aurais pu retourner a directement et mettre le type en Int, c’est juste pour montrer les possibilités …
- age_= : oui, ‘age_=’ est un identificateur valide en Scala. Cette méthode prend un paramètre de type Int, et teste s’il est positif, auquel cas elle affecte sa valeur au champ a, autrement elle lance une exception.
Voici maintenant un bout de code qui teste ce qu’on a fait:
p.age qui est passé à println invoque bel et bien le getter qu’on a défini, et retourne « 25 ans ».
p.age = 15 invoque le setter et ça passe, vu que 15 > 0
p.age = -5 lance une exception, comme prévu
2. Objets
2.1. Définition
Passons maintenant aux objets.
Les objets sont une notion spécifique à Scala (par rapport à Java) et se présentent entre autres comme remplaçant du contexte static de Java (inexistant dans Scala).
Dans la section précédente, j’ai montré comment définir une classe (une partie du moins).
Une classe ne définit que la structure de l’entité, mais pour l’utiliser, il faut l’instancier dans un conteneur pour pouvoir la manipuler.
Un objet est tout comme une classe dans la mesure où il définit la structure, mais il définit aussi l’instance, ce qui permet de le manipuler directement, sans avoir à l’instancier dans un conteneur.
Seulement, dans Scala, il s’agit plutôt d’un Singleton, crée, et maintenu par le compilateur.
Un objet est crée la première fois qu’on y accède, et c’est cette même et unique instance qui sera partagée entre tous ses clients.
2.2. Exemples
a. La méthode main
L’exemple le plus courant de l’utilisation des objets dans Scala est tout comme Java, pour définir la fonction main, le point d’entrée d’un programme.
Une telle méthode doit obligatoirement être définie dans un Objet, comme dans le montre cet exemple:
La déclaration d’un objet utilise object au lieu de class.
b. Le cas général
Voici un autre exemple où l’on définit une méthode utilitaire (square) pour pouvoir l’invoquer sans avoir à instantier une classe (comme Math de Java):
On peut dès lors accéder directement à la méthode square:
Pour tout le reste, les objets et les classes dans Scala se présentent de la même façon, excepté l’absence de constructeurs, qui n’ont plus aucun sens dans un objet.
3. Conclusion
Voilou, c’est la fin de ce (long) billet, qui a présenté quelques notions de base sur la définition et l’utilisation des classes et des objets dans Scala.
Le billet suivant de la série « Introduction à Scala » couvrirait les méthodes et les fonctions, qui sont des entités à part entière dans Scala et permettent de faire des choses très intéressantes …
Changelog
=> 11/07/08: J’ai corrigé la partie sur les constructeurs suite à un retour de gorgonite (à propos du currying). Merci à lui.