Come si può rendere immutabile una classe Java, qual è la necessità dell'immutabilità e c'è qualche vantaggio nell'usarla?
Come si può rendere immutabile una classe Java, qual è la necessità dell'immutabilità e c'è qualche vantaggio nell'usarla?
Risposte:
Cos'è un oggetto immutabile?
Un oggetto immutabile è uno che non cambierà stato dopo che è stato istanziato.
Come rendere immutabile un oggetto?
In generale, un oggetto immutabile può essere creato definendo una classe che non ha nessuno dei suoi membri esposto e non ha setter.
La seguente classe creerà un oggetto immutabile:
class ImmutableInt {
private final int value;
public ImmutableInt(int i) {
value = i;
}
public int getValue() {
return value;
}
}
Come si può vedere nell'esempio sopra, il valore di ImmutableInt
può essere impostato solo quando l'oggetto è istanziato, e avendo solo un getter ( getValue
) lo stato dell'oggetto non può essere cambiato dopo l'istanza.
Tuttavia, è necessario prestare attenzione che anche tutti gli oggetti a cui fa riferimento l'oggetto debbano essere immutabili, altrimenti potrebbe essere possibile modificare lo stato dell'oggetto.
Ad esempio, consentire un riferimento a un array o ArrayList
essere ottenuto tramite un getter consentirà allo stato interno di cambiare cambiando l'array o la raccolta:
class NotQuiteImmutableList<T> {
private final List<T> list;
public NotQuiteImmutableList(List<T> list) {
// creates a new ArrayList and keeps a reference to it.
this.list = new ArrayList(list);
}
public List<T> getList() {
return list;
}
}
Il problema con il codice di cui sopra è che ArrayList
può essere ottenuto getList
e manipolato, portando lo stato dell'oggetto stesso ad essere alterato, quindi non immutabile.
// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));
// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");
Un modo per aggirare questo problema è restituire una copia di un array o di una raccolta quando viene chiamato da un getter:
public List<T> getList() {
// return a copy of the list so the internal state cannot be altered
return new ArrayList(list);
}
Qual è il vantaggio dell'immutabilità?
Il vantaggio dell'immutabilità deriva dalla concorrenza. È difficile mantenere la correttezza negli oggetti mutabili, poiché più thread potrebbero tentare di cambiare lo stato dello stesso oggetto, portando alcuni thread a vedere uno stato diverso dello stesso oggetto, a seconda della tempistica delle letture e delle scritture su detto oggetto.
Avendo un oggetto immutabile, è possibile garantire che tutti i thread che stanno guardando l'oggetto vedranno lo stesso stato, poiché lo stato di un oggetto immutabile non cambierà.
return Collections.unmodifiableList(list);
restituire una visualizzazione di sola lettura nell'elenco.
final
. Altrimenti può essere esteso con un metodo setter (o altri tipi di metodi mutanti e campi mutabili).
final
se la classe ha solo private
campi poiché non è possibile accedervi dalle sottoclassi.
Oltre alle risposte già fornite, consiglierei di leggere sull'immutabilità in Effective Java, 2a edizione, poiché ci sono alcuni dettagli che sono facili da perdere (ad esempio copie difensive). Inoltre, Efficace Java 2nd Ed. è una lettura obbligata per ogni sviluppatore Java.
Rendi una classe immutabile come questa:
public final class Immutable
{
private final String name;
public Immutable(String name)
{
this.name = name;
}
public String getName() { return this.name; }
// No setter;
}
Di seguito sono riportati i requisiti per rendere immutabile una classe Java:
final
(in modo che le classi figlie non possano essere create)final
(in modo che non sia possibile modificarne il valore dopo la creazione dell'oggetto)Le classi immutabili sono utili perché
: sono thread-safe.
- Esprimono anche qualcosa di profondo sul tuo design: "Non posso cambiare questo"., Quando si applica, è esattamente ciò di cui hai bisogno.
L'immutabilità può essere ottenuta principalmente in due modi:
final
attributi di istanza per evitare la riassegnazioneI vantaggi dell'immutabilità sono i presupposti che puoi fare su questi oggetti:
Le classi immutabili non possono riassegnare i valori dopo che sono state istanziate. Il costruttore assegna i valori alle sue variabili private. Fino a quando l'oggetto non diventa nullo, i valori non possono essere modificati a causa dell'indisponibilità dei metodi setter.
essere immutabile dovrebbe soddisfare i seguenti,
/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {
// make the variables private
private String Name;
//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}
//provide getters to access values
public String getName() {
return this.Name;
}
}
Vantaggi: gli oggetti immutabili contengono i suoi valori inizializzati finché non muore.
Le classi immutabili sono quelle i cui oggetti non possono essere modificati dopo la creazione.
Le classi immutabili sono utili per
Esempio
String Class
Esempio di codice
public final class Student {
private final String name;
private final String rollNumber;
public Student(String name, String rollNumber) {
this.name = name;
this.rollNumber = rollNumber;
}
public String getName() {
return this.name;
}
public String getRollNumber() {
return this.rollNumber;
}
}
Come si può rendere immutabile una classe Java?
Da JDK 14+ che ha JEP 359 , possiamo usare " records
". È il modo più semplice e veloce per creare una classe Immutabile.
Una classe record è un vettore trasparente e poco modificabile per un insieme fisso di campi noto come record components
che fornisce una state
descrizione per il record. Ciascuno component
dà origine a un final
campo che contiene il valore fornito e unaccessor
metodo per recuperare il valore. Il nome del campo e il nome dell'accessorio corrispondono al nome del componente.
Consideriamo l'esempio della creazione di un rettangolo immutabile
record Rectangle(double length, double width) {}
Non è necessario dichiarare alcun costruttore, non è necessario implementare metodi uguali e hashCode. Qualsiasi record richiede un nome e una descrizione dello stato.
var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1
Se vuoi convalidare il valore durante la creazione dell'oggetto, dobbiamo dichiarare esplicitamente il costruttore.
public Rectangle {
if (length <= 0.0) {
throw new IllegalArgumentException();
}
}
Il corpo del record può dichiarare metodi statici, campi statici, inizializzatori statici, costruttori, metodi di istanza e tipi annidati.
Metodi di istanza
record Rectangle(double length, double width) {
public double area() {
return this.length * this.width;
}
}
campi statici, metodi
Poiché lo stato dovrebbe far parte dei componenti, non possiamo aggiungere campi istanza ai record. Tuttavia, possiamo aggiungere campi e metodi statici:
record Rectangle(double length, double width) {
static double aStaticField;
static void aStaticMethod() {
System.out.println("Hello Static");
}
}
qual è il bisogno di immutabilità e c'è qualche vantaggio nell'usarlo?
Le risposte pubblicate in precedenza sono abbastanza buone da giustificare la necessità di immutabilità e sono vantaggiose
Un altro modo per creare oggetti immutabili è usare la libreria Immutables.org :
Supponendo che siano state aggiunte le dipendenze richieste, creare una classe astratta con metodi di accesso astratti. Puoi fare lo stesso annotando con interfacce o anche annotazioni (@interface):
package info.sample;
import java.util.List;
import java.util.Set;
import org.immutables.value.Value;
@Value.Immutable
public abstract class FoobarValue {
public abstract int foo();
public abstract String bar();
public abstract List<Integer> buz();
public abstract Set<Long> crux();
}
È ora possibile generare e quindi utilizzare l'implementazione immutabile generata:
package info.sample;
import java.util.List;
public class FoobarValueMain {
public static void main(String... args) {
FoobarValue value = ImmutableFoobarValue.builder()
.foo(2)
.bar("Bar")
.addBuz(1, 3, 4)
.build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}
int foo = value.foo(); // 2
List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
}
}
Una classe immutabile è semplicemente una classe le cui istanze non possono essere modificate.
Tutte le informazioni contenute in ogni istanza sono fissate per la durata dell'oggetto, quindi non è mai possibile osservare modifiche.
Le classi immutabili sono più facili da progettare, implementare e utilizzare rispetto alle classi modificabili.
Per rendere una classe immutabile, segui queste cinque regole:
Non fornire metodi che modificano lo stato dell'oggetto
Assicurati che la classe non possa essere estesa.
Rendi definitivi tutti i campi.
Rendi privati tutti i campi.
Garantire l'accesso esclusivo a qualsiasi componente modificabile.
Gli oggetti immutabili sono intrinsecamente thread-safe; non richiedono sincronizzazione.
Gli oggetti immutabili possono essere condivisi liberamente.
Gli oggetti immutabili sono ottimi elementi costitutivi per altri oggetti
@ Jack, avere campi finali e setter in classe non renderà la classe immutabile. la parola chiave finale si assicura solo che una variabile non venga mai riassegnata. È necessario restituire una copia completa di tutti i campi nei metodi getter. Questo farà in modo che, dopo aver ottenuto un oggetto dal metodo getter, lo stato interno dell'oggetto non venga disturbato.
In qualità di non madrelingua inglese, non mi piace la comune interpretazione di "classe immutabile" che significa "oggetti di classe costruiti sono immutabili"; piuttosto, io stesso propendo di interpretarlo come "l'oggetto classe stesso è immutabile".
Detto questo, "classe immutabile" è una sorta di oggetto immutabile. La differenza è quando si risponde qual è il vantaggio. A mia conoscenza / interpretazione, la classe immutabile impedisce ai suoi oggetti di modificare il comportamento in fase di esecuzione.
La maggior parte delle risposte qui sono buone e alcune hanno menzionato le regole, ma ritengo che sia utile esprimere a parole perché e quando dobbiamo seguire queste regole. Quindi sto dando la spiegazione di seguito
E ovviamente se proviamo a usare i setter per quelle variabili finali, il compilatore genera un errore.
public class ImmutableClassExplored {
public final int a;
public final int b;
/* OR
Generally we declare all properties as private, but declaring them as public
will not cause any issues in our scenario if we make them final
public final int a = 109;
public final int b = 189;
*/
ImmutableClassExplored(){
this. a = 111;
this.b = 222;
}
ImmutableClassExplored(int a, int b){
this.a = a;
this.b= b;
}
}
Dobbiamo dichiarare la classe come "finale"?
1. Avere solo membri primitivi: non abbiamo problemi Se la classe ha solo membri primitivi, non è necessario dichiarare la classe come finale.
2. Avere oggetti come variabili membro: se abbiamo oggetti come variabili membro, dobbiamo rendere definitivi anche i membri di quegli oggetti. Significa che dobbiamo attraversare in profondità l'albero e rendere tutti gli oggetti / primitive come finali, cosa che potrebbe non essere possibile tutte le volte. Quindi la soluzione alternativa è rendere la classe finale che impedisce l'ereditarietà. Quindi non si tratta di sottoclasse che sovrascrivono i metodi getter.
L'annotazione @Value di Lombok può essere utilizzata per generare classi immutabili. È semplice come il codice qui sotto.
@Value
public class LombokImmutable {
int id;
String name;
}
Come da documentazione sul sito di Lombok:
@Value è la variante immutabile di @Data; tutti i campi vengono resi privati e definitivi per impostazione predefinita e i setter non vengono generati. Anche la classe stessa viene resa finale per impostazione predefinita, perché l'immutabilità non è qualcosa che può essere forzato in una sottoclasse. Come @Data, vengono generati anche metodi utili per String (), equals () e hashCode (), ogni campo ottiene un metodo getter e viene generato anche un costruttore che copre ogni argomento (eccetto i campi finali che vengono inizializzati nella dichiarazione del campo) .
Un esempio completamente funzionante può essere trovato qui.
record
possibile utilizzare Java ?