Introduzione
Questo articolo fornisce una panoramica su come creare una classe immutabile nella programmazione Java.
Un oggetto è immobile quando il suo stato non cambia dopo essere stato inizializzato. Ad esempio, String
è una classe immutabile e, una volta istanziato, il valore di un oggetto String
non cambia mai. Scopri di più su perché la classe String
è immutabile in Java.
Poiché un oggetto immutabile non può essere aggiornato, i programmi devono creare un nuovo oggetto per ogni cambio di stato. Tuttavia, gli oggetti immutabili hanno anche i seguenti vantaggi:
- Una classe immutabile è utile per scopi di caching perché non è necessario preoccuparsi dei cambiamenti di valore.
- Una classe immutabile è intrinsecamente thread-safe, quindi non è necessario preoccuparsi della sicurezza dei thread in ambienti multithreaded.
Scopri di più su multithreading in Java e sfoglia le Domande di Intervista sul Multithreading in Java.
Creazione di una classe immutabile in Java
Per creare una classe immutabile in Java, è necessario seguire questi principi generali:
- Dichiarare la classe come
final
in modo che non possa essere estesa. - Rendere tutti i campi
private
in modo che l’accesso diretto non sia consentito. - Non fornire metodi setter per le variabili.
- Rendere tutti i campi mutabili
final
in modo che il valore di un campo possa essere assegnato solo una volta. - Inizializzare tutti i campi utilizzando un metodo costruttore che esegue la copia profonda.
- Eseguire clonazione degli oggetti nei metodi getter per restituire una copia anziché restituire il riferimento all’oggetto effettivo.
La seguente classe è un esempio che illustra i concetti base dell’immutabilità. La classe FinalClassExample
definisce i campi e fornisce il metodo costruttore che utilizza la copia profonda per inizializzare l’oggetto. Il codice nel metodo main
del file FinalClassExample.java
testa l’immutabilità dell’oggetto.
Creare un nuovo file chiamato FinalClassExample.java
e copiare il seguente codice:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// campi della classe FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Funzione getter per oggetti mutabili
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// Metodo costruttore che esegue una copia profonda
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// La parola chiave "this" si riferisce all'oggetto corrente
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
// Test della classe immutabile
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// stampa i valori ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// cambia i valori delle variabili locali
i=20;
s="modified";
h1.put("3", "third");
// stampa nuovamente i valori
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compila ed esegui il programma:
- javac FinalClassExample.java
- java FinalClassExample
Nota: Potresti ricevere il seguente messaggio durante la compilazione del file: Nota: FinalClassExample.java utilizza operazioni non controllate o non sicure
perché il metodo getter sta utilizzando un cast non controllato da HashMap<String,String>
a Oggetto
. Puoi ignorare l’avviso del compilatore ai fini di questo esempio.
Ottieni il seguente output:
OutputPerforming Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
L’output mostra che i valori di HashMap non sono cambiati perché il costruttore utilizza una copia profonda e la funzione getter restituisce un clone dell’oggetto originale.
Cosa succede quando non si utilizza la copia profonda e il clonaggio
Puoi apportare modifiche al file FinalClassExample.java
per mostrare cosa succede quando si utilizza la copia superficiale invece della copia profonda e restituire l’oggetto invece di una copia. L’oggetto non è più immutabile e può essere modificato. Apporta le seguenti modifiche al file di esempio (o copia e incolla dall’esempio di codice):
- Elimina il metodo costruttore che fornisce la copia profonda e aggiungi il metodo costruttore che fornisce la copia superficiale evidenziato nel seguente esempio.
- Nella funzione getter, elimina
return (HashMap<String, String>) testMap.clone();
e aggiungireturn testMap;
.
Il file di esempio dovrebbe ora apparire così:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// campi della classe FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Funzione getter per oggetti mutabili
public HashMap<String, String> getTestMap() {
return testMap;
}
// Metodo costruttore che esegue la copia superficiale
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
// Testa la classe immutabile
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// stampa i valori di ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// cambia i valori delle variabili locali
i=20;
s="modified";
h1.put("3", "third");
// stampa nuovamente i valori
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compila ed esegui il programma:
- javac FinalClassExample.java
- java FinalClassExample
Ecco l’output che hai ottenuto:
OutputPerforming Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
L’output mostra che i valori della HashMap sono stati modificati perché il metodo del costruttore utilizza una copia superficiale, creando un riferimento diretto all’oggetto originale nella funzione getter.
Conclusione
Hai appreso alcuni dei principi generali da seguire nella creazione di classi immutabili in Java, compresa l’importanza della copia profonda. Continua il tuo apprendimento con altri tutorial Java.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java