Programmation orientée objet
Classes, objects, companion, héritage, traits, case classes. L'objectif : lire et comprendre un design Scala, pas réciter la syntaxe.
📄 Ressources du cours
🎥 Vidéos recommandées
-
🇫🇷
Scala - Matching Using Case Classes
Tutorialspoint · 06:56 · 3k vues — Scala - Matching Using Case ClassesWatch more Videos at https://www.tutorialspoint.com/videotutorials/index.htmLecture By: Mr. Arnab Chakraborty, Tutorials P
-
🇫🇷
Case Classes in Scala | Scala Traits
DataFlair · 24:21 · 37 vues — Learn ScalaFree Scala Course in English:- https://data-flair.training/courses/free-scala-course/Traits in ScalaCase Classes in ScalaWhat are Traits in Scala?...
-
🇬🇧
Scala Tutorial For Beginners | Scala Programming | OOPs and Scala Traits | Spark Training | Edureka
edureka! · 01:19:31 · 44k vues — ( Apache Spark Training - https://www.edureka.co/apache-spark-scala-certification-training ) This Scala Tutorial will help you understand Object Oriented Pro...
1. Class — la brique de base
- Valeurs / variables (avec
valouvar) - Méthodes : fonctions dont l'instance est implicitement un paramètre via
this
class ClassName[ParameterTypes*] (typedParameter*) {
classBody
}
ClassNamecommence par une majusculeclassBodycontient des déclarationsval/var/def- Typiquement dans
ClassName.scala(sauf sealed) - Instanciation :
new(comme Java)
Déclarations spéciales
| Mot-clé | Comportement |
|---|---|
final class | Comme Java : ne peut pas avoir de sous-types |
sealed class | Tous les sous-types doivent être dans la même unité de compilation (même fichier). Permet de vérifier l'exhaustivité d'un pattern matching. |
case class | Classe conçue pour le pattern matching. Voir section 5. |
abstract class | Comme Java : non instanciable |
Héritage : extends et with
extends: première superclasse (ou trait implémenté)with: pour chaque autre trait implémenté- Comme Java, une classe peut étendre au plus une classe.
Visibilité & immutabilité des membres
Visibilité par défaut : public. Préfixer la déclaration avec private la rend privée.
scala> class A {
val x : Int = 2
private val ans : Int = 42
}
scala> (new A()).x // 2 - accessible
scala> (new A()).ans // error: value ans cannot be accessed
2. Object — singleton immutable
object est une classe avec une seule instance immutable
Conséquences directes :
- Un objet ne peut pas être type-dépendant (pas de
[T]) - Un objet ne peut pas être parameter-dépendant (pas de paramètres de constructeur)
- Cette instance est traitée comme une lazy value
object ObjectName { objectBody }
Companion object — le compagnon
Object avec le même nom qu'une classe (typiquement dans le même fichier).
- La classe peut accéder aux membres privés de son companion object
- Les membres du companion ne dépendent pas d'une instance
static en Java.
Variantes
case object— case class + object (ex :None)extends/with: héritage comme une classe
3. Appel de méthode
Notation Java-like :
instance.methodName(parameters)
Les paramètres peuvent être appelés dans un autre ordre s'ils sont nommés :
(new A()).f(2, 3) // 5
(new A()).f(y = 3, x = 2) // 5
Notation infixe :
instance methodName parameter
≡ instance.methodName(parameter)
Seulement pour méthode à un seul paramètre. Utile pour les DSL (ex : sbt).
class A(x : Int) {
def +(y : Int) = x + y
}
(new A(2)) + 3 // 5
4. Constructeurs
Constructeur principal
- Paramètres : la liste de paramètres de la classe
- Corps : tout le corps de la classe
| Modificateur | Visibilité | Mutabilité |
|---|---|---|
| (rien) | privé | immuable |
val | public | immuable |
var | public | mutable |
private val | privé | immuable |
private var | privé | mutable |
// x est private par defaut : impossible d'y acceder de l'exterieur
scala> class A(x : Int) { def test = println(this.x) }
scala> (new A(2)).test // 2 (acces interne OK)
scala> (new A(2)).x // error: value x is not a member of A
// avec val : public immuable
scala> class A(val x : Int)
scala> (new A(2)).x // 2 ✓
// avec var : public mutable
scala> class A(var x : Int)
scala> val a = new A(2) ; a.x = 3 // ok
Constructeurs auxiliaires
Même principe qu'en Java :
- Méthode dont le nom est
this - Signature différente des autres constructeurs
- Tout appel interne à un constructeur se fait par le keyword
this
class A(x : Int, y : Int) {
def this(x : Int) = this(x, 0) // y vaut 0 par defaut
}
new A(1, 2) // principal
new A(1) // auxiliaire
5. Case class — la star du chapitre
case class est une classe « + ... »
Quatre cadeaux par rapport à une classe normale :
- Modificateur
valimplicitement appliqué à tout paramètre → public immuable ==compare le contenu, pas la référence- Méthode
toStringautomatiquement bien rédigée - Companion object implicite avec :
applyaux mêmes paramètres → plus besoin denew:Person(...)≡Person.apply(...)unapplyqui permet l'utilisation dans une clausecase(d'où le nom)
Exemple intégral à comprendre
scala> case class Person(last : String, first : String)
// 1) Pas de new — apply implicite
scala> val rdl = Person("Dujol", "Romain")
rdl: Person = Person(Dujol,Romain) // toString bien rédigé
// 2) == compare le contenu
scala> rdl == Person("Dujol", "Romain")
res0: Boolean = true // !
// 3) Accès public aux fields
scala> rdl.first + " " + rdl.last
res1: String = Romain Dujol
// 4) Pattern matching natif
scala> Person("Romain", "Dujol") match {
case Person("Dujol", "Romain") => "bibi"
case Person("Dujol", _ ) => "famille"
case Person(l, f) if l == f => "Ca existe ?"
case _ => "autre"
}
res2: String = autre
Combo gagnant : sealed + case classes
Classe abstraite
sealed + sous-types tous en case class (ou case object).Avantage : le compilateur vérifie l'exhaustivité du pattern matching.
scala> sealed abstract class IntOption
scala> case class IntSome(x : Int) extends IntOption
scala> case object IntNone extends IntOption
scala> val test : IntOption = IntNone
scala> test match { case IntNone => 0 }
// warning: match may not be exhaustive.
// It would fail on the following input: IntSome(_)
res1: Int = 0
Le warning n'apparaît pas si IntOption n'est pas sealed — c'est exactement ce que sealed apporte.
6. Traits — interfaces enrichies
- Pas de paramètres (jusqu'à Scala 3)
- Abstrait : pas d'instanciation directe avec
new - Pas de constructeur
- Une classe/un trait peut implémenter plusieurs traits
- Contenir des valeurs / variables
- Contenir des implémentations partielles
- Être implémenté par une seule instance (pas toute la classe)
Exemple : trait Comparable — Java style vs Scala style
Java style — toutes les méthodes abstraites, la classe doit toutes les définir :
trait Comparable[T] {
def == (that : T)
def != (that : T)
def < (that : T)
def <= (that : T)
def > (that : T)
def >= (that : T)
}
Scala style — implémentations partielles dérivées, la classe ne définit que == et < :
trait Comparable[T] {
def == (that : T)
def != (that : T) = !(this == that)
def < (that : T)
def <= (that : T) = (this < that) || (this == that)
def > (that : T) = !(this <= that)
def >= (that : T) = !(this < that)
}
Trait avec val/var et implémentation par une instance
scala> trait Person { val last : String; val first : String }
scala> case class Employee(last : String, first : String, department : String) extends Person
scala> val rdl : Person = Employee("Dujol", "Romain", "Mathematics")
scala> rdl.last
res0: String = Dujol
// Implementation sur une instance unique (sans toucher la classe)
scala> trait Employable { val department : String }
scala> case class P(last : String, first : String)
scala> val rdl = P("Dujol", "Romain") with Employable {
override val department = "Mathematics"
}
scala> rdl.department
res0: String = Mathematics
7. Génériques — bornes de type
Comme pour les méthodes, le type peut être inféré à l'usage. On peut borner ce que T peut être :
| Borne | Syntaxe | Signification |
|---|---|---|
| Upper bound | class A[T <: T1] | T doit étendre (ou implémenter) T1 |
| Lower bound | class A[T >: T0] | T0 doit étendre (ou implémenter) T |
| Les deux | class A[T <: T1 >: T0] | Combinaison des deux |
- Upper bound : assurer que tout élément de type
Tpossède certaines propriétés (celles deT1). Ex :T <: Comparable[T]garantit qu'on peut comparer. - Lower bound : pour les paramètres contravariants — typiquement définition de méthodes sur collections.
★ Réviser le chapitre
🃏 Flashcards — axées comportement
class vs object ?class = modèle d'instances multiples (avec new). object = une seule instance immuable, lazy. L'object remplace le static Java.class A(x : Int) est…val → public immuable. var → public mutable. private val / private var idem privés.case class ?val implicit sur tout paramètre · ② == compare le contenu · ③ toString bien rédigé · ④ companion avec apply (plus de new) et unapply (pattern matching).sealed + case class/object ?Option = Some | None). Le compilateur vérifie l'exhaustivité des patterns matchés.def +(y : Int) en notation infixe ?a + b au lieu de a.+(b). Seulement pour méthode à un seul paramètre. C'est ce qui rend les DSL Scala (sbt) si fluides.extends Trait1 with Trait2 with Trait3 .... Une classe étend au plus une classe, mais autant de traits qu'elle veut.T <: T1 (upper) : T étend T1 → T a au moins les capacités de T1. T >: T0 (lower) : T0 étend T → T est plus général que T0.Person("Dujol", "Romain") == Person("Dujol", "Romain") avec case class Person ?true — la case class redéfinit == pour comparer le contenu. Sans case, ce serait false (comparaison de références).this. Sa signature doit être différente des autres. Doit appeler this(...) en interne.sealed vs final ?final : pas de sous-types du tout. sealed : sous-types autorisés, mais dans le même fichier seulement. Sealed reste extensible localement.with sur une instance ?✎ Quiz éclair — comprendre ce que fait le code
class P(x : Int). Que vaut (new P(5)).x ?case class P(x : Int) ; val a = P(1) ; val b = P(1) ; a == bobject Singleton { var n = 0 ; def inc() = { n += 1 } }. Si on appelle Singleton.inc() 3 fois, Singleton.n vaut :sealed abstract class S ; case class A(x : Int) extends S ; case object B extends S. Le code val s : S = A(1) ; s match { case B => "B" } donne :trait T { val name : String ; def hello = s"Hi $name" } est :class A[T <: Comparable[T]] signifie :case class P(x : Int) ; P(5) match { case P(n) => n * 2 } donne :Score : 0 / 8 ·
❓ Q/R — décisions de design
Class normale ou case class : comment choisir ?
- case class si l'objet représente une valeur de donnée (un point, une transaction, un message Akka). On veut comparer le contenu, le pattern-matcher, le sérialiser facilement.
- class normale si l'objet porte un comportement / un état mutable / des invariants complexes. Ex : un
Accountavecdeposit/withdraw.
equals/hashCode, tu veux probablement une case class.
Trait ou classe abstraite : quelle différence pratique ?
- Classe abstraite : on n'en hérite que d'une seule. Peut avoir un constructeur avec paramètres.
- Trait : on peut en hériter de plusieurs (
with). Pas de constructeur avec paramètres (Scala 2). Peut être mixé sur une instance unique.
Pourquoi le combo sealed + case class est si présent dans le code Scala ?
sealed abstract class Tree
case class Leaf(value : Int) extends Tree
case class Node(left : Tree, right : Tree) extends Tree
sealedgarantit qu'on liste toutes les variantes dans le même fichier- Pattern matching exhaustif vérifié à la compilation
- Construction sans
new(apply implicite) - Décomposition propre via case class
Quelle différence entre une case class et un case object ?
- case class avec paramètres : sert à porter des données. Une instance par valeur (ex :
Some(42)≠Some(43)). - case object : pour les variants sans donnée, instance unique. Ex :
None— il n'y a qu'unNonedans l'univers, pas besoin de paramètres.
À l'examen, on me donne du code. Que regarder en premier ?
- Le type de chaque expression — Scala l'affiche dans le REPL, mais à la main il faut savoir le déduire.
- Le pattern matching — quelle branche match ? Y a-t-il un
ifguard ? - Mutable vs immutable —
varvsval,case classpartage les champs ? - Effet de bord ? — y a-t-il un
println, une variable mutée, unThread.sleep? - L'ordre d'évaluation — by value (immédiat) vs by name (
=>) vs lazy (lazy val).