Objets mutables et immuables
1. Introduction
Les objets mutables sont des objets qui peuvent être modifiés après leur création, contrairement aux objets immuables, qui eux, ne peuvent pas être modifiés.
En Java, la plupart des objets sont mutables, à quelques exceptions près,
comme les classes URL et File et les classes-enveloppe :
Integer, Byte, Float, Char, etc.
| Les Classes-enveloppe ou Wrappers, sont des classes qui encapsulent des données de type primitif, pour les utiliser comme des objets ordinaires. |
Par exemple, la classe String est immuable, contrairement à StringBuilder, qui elle est mutable.
Considérons l’extrait Java suivant :
String message = "In Java, String objects are ";
message = message + "immutable";
StringBuilder builder = new StringBuilder();
builder.append("StringBuilder objects are ")
.append("mutable");
La variable message sera d’abord assignée à un objet (ligne 1),
puis à un autre objet (ligne 2).
Après la deuxième affectation, le premier objet String sera perdu,
mais restera en mémoire jusqu’à ce que le ramasse-miettes fasse son travail et libère la mémoire.
De son côté, la variable builder ne sera assignée qu’une seule fois (ligne 4),
mais son contenu sera modifié après chaque appel de la méthode append().
Les objets mutables ont des méthodes de modification, telles que :
set(), append(), add(), etc.
1.1. Différences entre les objets mutables et immuables
| Mutables | Immuables |
|---|---|
peuvent prendre n’importe quelle valeur ou n’importe quel état sans créer de nouvel objet. |
ne peuvent pas être modifiés. |
fournissent des méthodes de modification pour changer leur contenu. |
ne fournissent aucune méthode de modification. |
peuvent ou non être sûrs pour les threads. |
sont sûrs pour les threads. |
ne doivent pas être utilisés en tant que clés dans un |
|
ont tendance à utiliser moins de mémoire que les objets immuables. |
2. Exercice : Écrire une classe mutable
Écrivez une classe mutable appelée MutableIntWrapper, qui implémente l’interface MutableInt,
présentée ci-dessous.
Cette classe doit implémenter des opérations arithmétiques (addition, soustraction, etc.),
ainsi que des opérations d’incrémentation et de décrémentation.
Voici un exemple d’utilisation de la classe MutableIntWrapper :
package fr.unantes.sce.examples;
import fr.unantes.sce.mutable.MutableInt;
import fr.unantes.sce.mutable.MutableIntWrapper;
import java.util.List;
import java.util.function.Consumer;
public class MutableIntExample {
public static void main(String[] args) {
MutableInt eight = new MutableIntWrapper(8);
MutableInt fifteen = new MutableIntWrapper(15);
eight.add(fifteen);
assert eight.get() == 23;
var list = List.of(1,2,3);
list.forEach(System.out::println);
}
}
L’interface à implémenter est la suivante :
MutableIntpackage fr.unantes.sce.mutable;
/**
* An interface representing a mutable integer value.
*/
public interface MutableInt {
/**
* Gets the current value of the mutable integer.
* @return The current value of the mutable integer.
*/
int get();
/**
* Sets a new value for the mutable integer.
* @param newValue The new value to set for the mutable integer.
*/
void set(int newValue);
/**
* Adds the value of the other mutable integer to the current value of this mutable integer.
* @param other The other mutable integer to add.
* @return The current mutable integer after adding the other mutable integer.
*/
MutableInt add(MutableInt other);
/**
* Subtracts the value of the other mutable integer from the current value of this mutable integer.
* @param other The other mutable integer to subtract.
* @return The current mutable integer after subtracting the other mutable integer.
*/
MutableInt subtract(MutableInt other);
/**
* Multiplies the value of the other mutable integer with the current value of this mutable integer.
* @param other The other mutable integer to multiply.
* @return The current mutable integer after multiplying the other mutable integer.
*/
MutableInt multiply(MutableInt other);
/**
* Divides the value of the current mutable integer by the value of the other mutable integer.
* @param other The other mutable integer to divide by.
* @return The current mutable integer after dividing by the other mutable integer.
*/
MutableInt divide(MutableInt other);
/**
* Increments the value of the mutable integer by 1.
*/
void increment();
/**
* Increments the value of the mutable integer by 1 and returns the updated value.
* @return The updated value after incrementing.
*/
int incrementAndGet();
/**
* Returns the current value of the mutable integer and then increments it by 1.
* @return The current value of the mutable integer before incrementing.
*/
int getAndIncrement();
/**
* Decrements the value of the mutable integer by 1.
*/
void decrement();
/**
* Decrements the value of the mutable integer by 1 and returns the updated value.
* @return The updated value after decrementing.
*/
int decrementAndGet();
/**
* Returns the current value of the mutable integer and then decrements it by 1.
* @return The current value of the mutable integer before decrementing.
*/
int getAndDecrement();
}
3. Exercice : Écrire une classe immuable
-
Implémentez une classe nommée
ImmutableInt, qui implémente des méthodes arithmétiques de base. -
Utilisez l’interface
Immutable, présentée ci-dessous, pour guider votre implémentation . -
Une fois la classe implémentée, suggérez des solutions pour éviter :
-
que les méthodes soient appelées avec un argument nul
-
un dépassement de capacité du champ entier de la classe
-
Immutablepackage fr.unantes.sce.immutable;
public interface Immutable<T> {
Immutable<T> add(Immutable<T> other);
Immutable<T> subtract(Immutable<T> other);
Immutable<T> multiply(Immutable<T> other);
Immutable<T> divide(Immutable<T> other);
T value();
}
Voici un exemple d’utilisation de la classe ImmutableInt :
ImmutableIntpackage fr.unantes.sce.examples;
import fr.unantes.sce.immutable.Immutable;
import fr.unantes.sce.immutable.ImmutableInt;
public class ImmutableIntExample {
public static void main(String[] args) {
Immutable<Integer> one = new ImmutableInt(1);
Immutable<Integer> twenty = new ImmutableInt(20);
Immutable<Integer> result = one.add(twenty);
assert result.value() == 21;
assert one.value() == 1;
}
}
4. Exercice : Optimisation de l’instantiation de ImmutableInt
Modifier l’implémentation de la classe ImutableInt pour la faire mettre en cache les valeurs les plus utilisées (de 0 à 255) et empêcher l’instanciation de plus d’une instance par valeur.
Pour cela, vous allez appliquer le patron de conception Poids mouche.
-
Créez un champ statique de type tableau d’entiers, de taille 256.
-
Initialisez ce champ avec des entiers immuables de 0 à 255.
-
Créez une méthode statique nommée
valueOf(int), qui vérifie si la valeur de l’argument est dans le cache. Si oui, elle retourne la valeur du cache. Si non, elle crée une nouvelle instance.-
La méthode
valueOf(int)joue le rôle d’une méthode fabrique
-
-
Rendez inaccessible le seul constructeur de la classe