Avant de commencer,
voyons la hiérarchie des classes en Scala :
En Scala (comme en Java), toute
classe écrite par un utilisateur hérite directement ou indirectement de la
classe AnyRef (Object pour Java). A la racine de toutes les classes Scala, on
retrouve Any. La classe AnyRef hérite donc de la classe Any. La classe Any dispose de deux sous-classes :
- AnyVal : c’est la classe mère des types dits "types valeurs" : Int, Char, Bool... Ils ont la
particularité qu'on ne peut dans aucun cas utiliser "new" pour
les créer, on peut juste utiliser des valeurs prédéfinies comme 1,
‘A’, false ;
- AnyRef : c’est la classe mère des types dits « types
références ». Toutes les classes de l’API standard héritent de cette
classe ainsi que toutes les classes créées par les utilisateurs.
Toutes les Value Classes héritent de la classe AnyVal.
Une Value Class est similaire à une classe « normale » mais
fonctionne comme un type simple. Elle permet de créer des wrappers des types simples (Int, String, …)
qui ne résultent pas en la création d'un objet à l'exécution. L'objectif
est de permettre la création de types de bas niveau pouvant se substituer aux
types de bases de Scala permettant ainsi un meilleur typage. Une Value class doit hériter
de la classe AnyVal. Les Values classes
ont plusieurs restrictions, notamment, elles ne peuvent pas avoir qu’un seul
constructeur avec un seul type simple (le type wrappé) ; elles ne peuvent
voir d’autres attributs (un état interne) ni définir de méthodes equals ou hashcode ….
Un exemple simple est
la création d’un wrapper du type simple String. Créons une classe simple sans
passer par les values class et analysons le résultat :
class StringWrapper(val param: String) {
def up() = param.toUpperCase
def isNullOrEmpty = param == null || param.length==0
def emptyToNull = if(isNullOrEmpty) null else param
}
Testons la classe :
//Main
def main(args: Array[String]) {
//création de 4 objets du type StringWrapper.
//Au niveau JVM, aucun objet n'est crée, tous sont reprsentés par de String
val w0 = new StringWrapper("sw0")
val w1 = new StringWrapper("sw1")
val w2 = new StringWrapper("sw2")
val w3 = new StringWrapper("sw3")
println(w0.up())
println(w1.up())
println(w2.isNullOrEmpty)
println(w3.up())
}
Inspectons les objets crées par VisualVM. VisualVM est un outil de monitoring graphique de JVM locale ou distante. Il est
intégré au JDK depuis la version 6 update 7. Vous le trouverez dans le
répertoire JAVA_HOME/bin/jvisualvm. Il permet notamment de connaître l’occupation
CPU, la taille de la heap, etc. Pour monitorer une JVM locale, il suffit de
lancer l’outil qui détectera les JVM en cours d’exécution.
Nous
constatons la création de 4 instances de la classe StringWrapper. Passons
maintenant par les values class (extends AnyVal) et analysons le résultat :
class StringWrapper(val param: String) extends AnyVal {
def up() = param.toUpperCase
def isNullOrEmpty = param == null || param.length==0
def emptyToNull = if(isNullOrEmpty) null else param
}
Nous pouvons
remarquer la création d’une seule instance de la classe StringWrapper. Cette instance permet d’appeler les
fonctions définies dans la classe StringWrapper
sur le type simple (String) de la Value Class (StringWrapper). La décompilation de la classe StringWrapper nous donne :
Pour finir, on peut juste noter qu’une Value Class peut hériter (plutôt être mixée) d’un trait universel. La documentation scala définit un trait universel comme : A universal trait is a trait that extends
public static class StringWrapper
{
private final String param;
public String param()
{
return this.param;
}
public String up()
{
return param().toUpperCase();
}
public boolean isNullOrEmpty()
{
return (param() == null) || (param().length() == 0);
}
public String emptyToNull()
{
null;return isNullOrEmpty() ? null : param();
}
public StringWrapper(String param) {}
}
Pour finir, on peut juste noter qu’une Value Class peut hériter (plutôt être mixée) d’un trait universel. La documentation scala définit un trait universel comme : A universal trait is a trait that extends
Any
, only has def
s as members,
and does no initialization. Un exemple d'une Value Class avec l'utilisation d'un Trait Universel :
def main(args: Array[String]) { //création de 4 objets du type StringWrapper. //Au niveau JVM, aucun objet n'est crée, tous sont reprsentés par de String val w0 = new StringWrapper("sw0") val w1 = new StringWrapper("sw1") val w2 = new StringWrapper("sw2") val w3 = new StringWrapper("sw3") println(w0.up()) w0.toPrint() println(w1.up()) w1.toPrint() println(w2.isNullOrEmpty) w2.toPrint() println(w3.up()) w3.toPrint() } class StringWrapper(val param: String) extends AnyVal with Printable { def up() = param.toUpperCase def isNullOrEmpty = param == null || param.length==0 def emptyToNull = if(isNullOrEmpty) null else param } trait Printable extends Any{ def toPrint() = print(this) }