Ricerca…


Osservazioni

Gli oggetti immutabili hanno uno stato fisso (nessun setter), quindi tutti gli stati devono essere noti al momento della creazione dell'oggetto.

Sebbene non sia tecnicamente necessario, è buona pratica rendere final tutti i campi. Questo renderà la classe immutable thread-safe (vedi Java Concurrency in Practice, 3.4.1).

Gli esempi mostrano diversi modelli che possono aiutare a raggiungere questo obiettivo.

Creare una versione immutabile di un tipo usando la copia difensiva.

Alcuni tipi e classi di base in Java sono fondamentalmente mutevoli. Ad esempio, tutti i tipi di array sono modificabili, così come le classi come java.util.Data . Questo può essere imbarazzante in situazioni in cui è imposto un tipo immutabile.

Un modo per affrontarlo consiste nel creare un wrapper immutabile per il tipo mutable. Ecco un semplice wrapper per una matrice di numeri interi

public class ImmutableIntArray {
    private final int[] array;
    
    public ImmutableIntArray(int[] array) {
        this.array = array.clone();
    }

    public int[] getValue() {
        return this.clone();
    }
}

Questa classe funziona usando la copia difensiva per isolare lo stato mutabile (l' int[] ) da qualsiasi codice che possa mutarlo:

  • Il costruttore usa clone() per creare una copia distinta dell'array dei parametri. Se il chiamante del costruttore ha successivamente modificato l'array di parametri, non avrebbe influenzato lo stato di ImmutableIntArray .

  • Il metodo getValue() usa anche clone() per creare l'array che viene restituito. Se il chiamante dovesse cambiare l'array dei risultati, non avrebbe influenzato lo stato di ImmutableIntArray .

Potremmo anche aggiungere metodi a ImmutableIntArray per eseguire operazioni di sola lettura sull'array avvolto; ad esempio ottenere la sua lunghezza, ottenere il valore in un indice particolare, e così via.

Si noti che un tipo di wrapper immutabile implementato in questo modo non è compatibile con il tipo originale. Non puoi semplicemente sostituire il primo per quest'ultimo.

La ricetta per una lezione immutabile

Un oggetto immutabile è un oggetto il cui stato non può essere modificato. Una classe immutabile è una classe le cui istanze sono immutabili per progettazione e implementazione. La classe Java che viene presentata più comunemente come esempio di immutabilità è java.lang.String .

Quello che segue è un esempio stereotipato:

public final class Person {
    private final String name;
    private final String ssn;     // (SSN == social security number)

    public Person(String name, String ssn) {
        this.name = name;
        this.ssn = ssn;
    }

    public String getName() {
        return name;
    }
   
    public String getSSN() {
        return ssn;
    }
}

Una variazione su questo è dichiarare il costruttore come private e fornire invece un metodo public static .


La ricetta standard per una classe immutabile è la seguente:

  • Tutte le proprietà devono essere impostate nel costruttore o nei metodi di fabbrica.
  • Non dovrebbero esserci setter.
  • Se è necessario includere setter per ragioni di compatibilità dell'interfaccia, non devono fare nulla o lanciare un'eccezione.
  • Tutte le proprietà dovrebbero essere dichiarate come private e final .
  • Per tutte le proprietà che fanno riferimento a tipi mutabili:
    • la proprietà deve essere inizializzata con una copia profonda del valore passato tramite il costruttore e
    • il getter della proprietà dovrebbe restituire una copia profonda del valore della proprietà.
  • La classe dovrebbe essere dichiarata come final per impedire a qualcuno di creare una sottoclasse mutevole di una classe immutabile.

Un paio di altre cose da notare:

  • L'immutabilità non impedisce all'oggetto di essere annullabile; ad es. null può essere assegnato a una variabile String .
  • Se le proprietà di una classe immutable sono dichiarate final , le istanze sono intrinsecamente thread-safe. Ciò rende le classi immutabili un buon esempio per l'implementazione di applicazioni multi-thread.

Difetti di progettazione tipici che impediscono a una classe di essere immutabile

Utilizzo di alcuni setter, senza impostare tutte le proprietà necessarie nel / i costruttore / i

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

È facile dimostrare che la classe Person non è immutabile:

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

Per risolvere il problema, elimina setSurname() e setSurname() il costruttore come segue:

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

Non contrassegnare le variabili di istanza come private e final

Dai un'occhiata alla seguente classe:

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

Il seguente frammento mostra che la suddetta classe non è immutabile:

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

Per risolvere il problema, contrassegna semplicemente la proprietà del nome come private e final .


Esporre un oggetto mutevole della classe in un getter

Dai un'occhiata alla seguente classe:

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Names classe dei Names sembra immutabile a prima vista, ma non è come mostra il seguente codice:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Ciò è accaduto perché una modifica all'Elenco di riferimento restituito da getNames() può modificare l'elenco effettivo di Names .

Per risolvere questo problema, è sufficiente evitare i riferimenti che fanno riferimento a oggetti mutabili della classe sia per l'esecuzione di copie difensive ritorno, come segue:

public List<String> getNames() {
   return new ArrayList<String>(this.names); // copies elements
}

o progettando getter in modo che vengano restituiti solo altri oggetti e primitive immutabili , come segue:

public String getName(int index) {
    return names.get(index);
}
public int size() {
    return names.size();
}

Iniezione del costruttore con oggetto (i) che può essere modificato al di fuori della classe immutabile

Questa è una variazione del difetto precedente. Dai un'occhiata alla seguente classe:

import java.util.List;
public final class NewNames {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = names;
    }
    public String getName(int index) {
        return names.get(index);
    }
    public int size() {
        return names.size();
    }
}

Come classe Names prima, anche la classe NewNames sembra immutabile a prima vista, ma non lo è, infatti il ​​seguente frammento dimostra il contrario:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Per risolvere questo problema, come nel difetto precedente, è sufficiente creare copie difensive dell'oggetto senza assegnarlo direttamente alla classe immutabile, ovvero il costruttore può essere modificato come segue:

    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }

Lasciando ignorare i metodi della classe

Dai un'occhiata alla seguente classe:

public class Person {
    private final String name;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
}

Person classe della Person sembra immutabile a prima vista, ma supponiamo che una nuova sottoclasse di Person sia definita:

public class MutablePerson extends Person {
    private String newName;
    public MutablePerson(String name) {
        super(name);            
    }
    @Override
    public String getName() {
        return newName;
    }
    public void setName(String name) {
        newName = name;
    }
}

ora la mutabilità di Person (im) può essere sfruttata attraverso il polimorfismo usando la nuova sottoclasse:

Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike    

Per risolvere questo problema, sia contrassegnare la classe come final in modo che non possa essere esteso o dichiarare tutto il suo costruttore (s) come private .



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow