Ook!, un langage de programmation... primitif.

Passer de Java à Scala – Partie 3 – Classes et objets compagnons

Passer de Java à Scala – Partie 3 – Classes et objets compagnons

Voici la 3è partie de cette série dédiée au passage de Java à Scala. Dans cet article, nous verrons en détail comment sont définies et utilisées les classes en Scala, les objets singleton et les objets appelés « compagnons ».

Retrouvez le code source des exemples ici : https://github.com/alexandrelanglais/scala-tutorial-3

Retrouvez tous les articles de la série « Passer de Java à Scala »

Les classes en Scala

Pour cet exemple, nous allons écrire une classe qui calcule le checksum d’une chaine de caractères.

La plus simple définition de classe peut s’écrire de la manière suivante

class ChecksumCalculator

L’instantiation de classe se fait via le mot-clé new :

val test = new ChecksumCalculator

Membres privés

Cette classe n’a que peu d’intérêt, donc voyons comment déclarer une variable encapsulée au sein de la classe :

class ChecksumCalculator {
  private var sum = 0;
}

La variable sum étant définie private, elle ne sera accessible que de l’intérieur de la classe.

Fonctions

Comme la variable sum est privée, nous ne pouvons pas agir dessus en-dehors de la classe. Créons une méthode add pour ajouter une valeur à la variable sum :

class ChecksumCalculator {
  private var sum = 0;

  def add(b: Byte) { sum += b }
}

La définition de fonctions se fait via le mot-clé def. Par défaut, les fonctions ont une visibilité publique (il n’y a pas de visibilité friendly au niveau du package).

La fonction add prend en paramètre un Byte qu’elle additionne à sum.

Ajoutons à cette classe la fonction de calcul de checksum en fonction de sum :

class ChecksumCalculator {
  private var sum = 0;

  def add(b: Byte) { sum += b }
  def checksum(): Int = ~(sum & 0XFF) + 1
}

Instantiation et usage

Nous avons maintenant 2 fonctions dans notre classe qui permettent de calculer le checksum d’une chaine de caractères. Voici comment nous pourrions l’utiliser dans une application :

import fr.demandeatonton.classes.{ChecksumCalculator}

object TestChecksumCalculator {
  def main(args: Array[String]): Unit = {
    val calc = new ChecksumCalculator
    val message = "Hello world!"

    for (c <- message)
      calc.add(c.toByte)

    val cs = calc.checksum()
    println(cs)
  }
}

Pour chaque caractère de la variable message, nous ajoutons à notre instance de classe la valeur en Byte du caractère, puis nous calculons le checksum et l’affichons.

Cela fait beaucoup de lignes de code pour calculer un checksum. Il serait plus commode de rajouter ce code dans une méthode static d’une classe Helper, mais les méthodes statiques n’existent pas en Scala.

L’objet compagnon

Pour palier à l’absence de méthodes statiques, Scala met à notre disposition un mécanisme nommé les objets compagnons de classe.

Ce sont des Object définis dans le même fichier que la classe, portant le même nom et dont les méthodes peuvent être appelées similairement aux méthodes statiques de Java.

object ChecksumCalculator {
  def calculate(s: String): Int = {

    val acc = new ChecksumCalculator
    for (c <- s)
      acc.add(c.toByte)

    val cs = acc.checksum()
    cs
  }
}

On peut appeler cette méthode comme ceci

object TestChecksumCalculator {
  def main(args: Array[String]): Unit = {
    val cs = ChecksumCalculator.calculate("Hello world!")

    println(cs)
  }
}

Les objects en Scala sont des singletons : l’intérêt de l’objet compagnon est donc d’avoir une classe qui se comporte comme si toutes ses méthodes étaient statiques.

L’une des différences entre les classes et les objets singletons est que les classes peuvent prendre des paramètres à leur construction, contrairement aux singletons.

Un objet singleton qui ne partage pas le même nom qu’une autre classe est appelée un objet standalone. L’objet TestChecksumCalculator contenant la méthode main en est un exemple.

Les paramètres de classes

Il est possible de définir une classe Scala en lui spécifiant des paramètres. Ces paramètres prendront la forme de variable membres, accessible depuis l’intérieur de la classe.

Voyons l’exemple d’une classe MyRectangle prenant une position x et y ainsi qu’une largeur (w) et une hauteur (h) :

class MyRectangle(x:Int, y:Int, w: Int, h: Int) {
  println("Created Rectangle of width and height " + w + "x" + h + " at " + x + ":" + y)
}

Tout code définit dans une classe et en dehors d’une fonction est considéré comme « code du constructeur ». Si on exécute le programme suivant :

object TestMyRectangle extends App {
  val r = new MyRectangle(10, 20, 15, 25)
}

On obtient en sortie :

Created Rectangle of width and height 15x25 at 10:20

Scala nous épargne dans une certaine mesure les getters et setters pour les classes simples.

Dans une certaine mesure, car si l’on veut pouvoir accéder aux valeurs x et y depuis l’extérieur de la classe, nous devons les définir dans la classe. En effet, on obtiendra un erreur de compilation si on essaye d’accéder aux champs de classe ainsi :

println(r.x) // erreur de compilation

On définit les membres de classe comme suit :

class MyRectangle(xPos:Int, yPos:Int, w: Int, h: Int) {
  val x = xPos
  val y = yPos
  val width = w
  val height = h

  println("Created Rectangle of width and height " + width + "x" + height + " at " + x + ":" + y)
}

Notez qu’on a du renommer les paramètres de la classe pour éviter les clash de noms. Maintenant le programme suivant compile :

object TestMyRectangle extends App {
  val r = new MyRectangle(10, 20, 15, 25)
  println(r.x)
}

Et affiche

Created Rectangle of width and height 15x25 at 10:20
10

Constructeurs auxiliaires

Le constructeur principal (le code qui suit la définition de classe) peut être aggrémenté d’un ou plusieurs constructeurs auxiliaires. Imaginons que nous voulions un constructeur de rectangle qui prend seulement sa taille et initialise sa position à 0:0. On peut écrire :

def this(w:Int, h: Int) = this(0, 0, w, h);

Contrairement à Java, la première instruction d’un constructeur auxiliaire en Scala doit être un appel au constructeur principal via this(..)

Cela garantit l’initialisation des variables membres de la classe.

Conclusion

C’est la fin de cette 3è partie de la série « Passer de Java à Scala ». Dans le prochain article, nous verrons les objets fonctionnels ou comment créer des classes immutables. Nous irons également un peu plus loin dans les possibilités offertes par les classes en Scala.

Laisser un commentaire