2009-06-11 13 views
5

Ho implementato una classe in Java che, internamente, memorizza un elenco. Voglio che la lezione sia immutabile. Tuttavia, ho bisogno di eseguire operazioni sui dati interni che non hanno senso nel contesto della classe. Quindi, ho un'altra classe che definisce un insieme di algoritmi. Ecco un esempio semplificato:Oggetti immutabili in Java e accesso ai dati

Wrapper.java

import java.util.List; 
import java.util.Iterator; 

public class Wrapper implements Iterable<Double> 
{ 
    private final List<Double> list; 

    public Wrapper(List<Double> list) 
    { 
     this.list = list; 
    } 

    public Iterator<Double> iterator() 
    { 
     return getList().iterator(); 
    } 

    public List<Double> data() { return getList(); } 
} 

Algorithm.java

import java.util.Iterator; 
import java.util.Collection; 

public class Algorithm 
{ 
    public static double sum(Collection<Double> collection) 
    { 
     double sum = 0.0; 
     Iterator<Double> iterator = collection.iterator(); 

     // Throws NoSuchElementException if the Collection contains no elements 
     do 
     { 
      sum += iterator.next(); 
     } 
     while(iterator.hasNext()); 

     return sum; 
    } 
} 

Ora, la mia domanda è: esiste un modo affidabile per evitare che qualcuno modifica i miei dati interni nonostante il fatto che la mia classe sia destinata a essere immutabile? Mentre Ho fornito un dato () metodo per scopi di sola lettura, non c'è nulla per impedire a qualcuno di modificare i dati tramite metodi come chiaro() e remove(). Ora, mi rendo conto che potrei fornire l'accesso ai miei dati esclusivamente tramite iteratori. Tuttavia, mi è stato detto che è tipico passare una collezione . In secondo luogo, se avessi un algoritmo che richiede più di un passaggio sui dati, dovrei fornire più iteratori, il che sembra una pendenza scivolosa.

Ok, si spera che ci sia una soluzione semplice che risolverà le mie preoccupazioni. Sto solo tornando in Java e non ho mai considerato queste cose prima di occuparmi di const in C++. Grazie in anticipo!

Oh! C'è un'altra cosa a cui ho appena pensato. Non posso praticamente restituire una copia dell'interno Elenco. L'elenco nell'elenco contiene in genere centinaia di migliaia di elementi.

risposta

23

È possibile utilizzare Collections.unmodifiableList e modificare il metodo dati.

public List<Double> data() { return Collections.unmodifiableList(getList()); } 

Dal javadoc:

Restituisce una vista immodificabile della lista specificata . Questo metodo consente ai moduli di fornire agli utenti l'accesso "in sola lettura" agli elenchi interni. operazioni di query sulla lista restituita "leggere attraverso" la lista specificata, e tentativi di modificare l'restituito lista, diretta o tramite il suo iteratore, si raggiunge un UnsupportedOperationException.

+0

Perfetto! Non l'ho visto. Grazie. –

+1

Devi ancora occuparti del modo in cui viene creato l'oggetto Wrapper: poiché l'elenco viene passato al costruttore, potrebbero esserci dei riferimenti ad esso nel codice. Se vuoi che Wrapper sia veramente immutabile, dovrai copiare il contenuto della lista. –

5

Java non ha un concetto sintattico di classe immutabile. Spetta a te, come programmatore, dare accesso alle operazioni, ma devi sempre presumere che qualcuno lo abuserebbe.

Un oggetto veramente immutabile non offre un modo per le persone di cambiare lo stato o di avere accesso a variabili di stato che possono essere utilizzate per cambiare stato. La tua classe non è immutabile come lo è ora.

Un modo per renderlo immutabile è restituire una copia della raccolta interna, nel qual caso è necessario documentarlo molto bene e avvisare le persone di utilizzarlo in codice ad alte prestazioni.

Un'altra opzione consiste nell'utilizzare una raccolta wrapper che genera eccezioni in fase di esecuzione se qualcuno ha provato a modificare il valore (non consigliato, ma possibile, vedere apache-collections per un esempio). Penso che anche la libreria standard ne abbia una (guarda sotto la classe Collections).

Una terza opzione, se alcuni client modificano i dati mentre altri no, è quella di fornire interfacce diverse per la classe. Diciamo che hai un IMyX e IMyImmutableX. Quest'ultima definisce solo le operazioni "sicure", mentre la prima la estende e aggiunge quelle non sicure.

Ecco alcuni suggerimenti su come rendere le lezioni immutabili. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

+0

Mi piace molto il suggerimento di Alex B. Come ho menzionato nel mio post, non posso restituire una copia dell'elenco *. Nella tua risposta, dici che non consigli di restituire una vista non modificabile della lista, come suggerito da Alex. Puoi per favore elaborare? –

+0

@Scott: C'è un approccio generale nella programmazione che dice "Non sorprendere i tuoi utenti" o "preferisci errori in fase di compilazione agli errori di runtime". In genere è preferibile che il compilatore trovi un problema piuttosto che arrestarlo in modo anomalo durante la produzione. Con un elenco non modificabile, si restituisce un elenco valido, l'elenco può essere passato in giro fino a molto tempo dopo può improvvisamente "esplodere" quando qualcuno tenta di modificarlo. – Uri

+0

@Scott: se è necessario restituire un elenco e non è possibile copiarlo, il costo che si dovrà sostenere è questa eccezione di runtime. Ma forse prendi in considerazione di nominare la tua funzione "getUnmodifiableList" o qualcosa del genere. La mia ricerca ha dimostrato che la maggior parte delle persone non leggerà mai i documenti di funzioni che sembrano intuitive, come data() – Uri

5

È possibile utilizzare Collections.unmodifiableList?

In base alla documentazione, verrà restituita una vista non modificabile (immutabile) di List. Ciò impedirà l'utilizzo di metodi come remove e add lanciando un UnsupportedOperationException.

Tuttavia, non vedo che non impedirà la modifica degli elementi effettivi nella lista stessa, quindi non sono abbastanza sicuro se questo è abbastanza immutabile. Almeno la lista stessa non può essere modificata.

Ecco un esempio in cui i valori interni della List restituito da unmodifiableList possono comunque essere modificati:

class MyValue { 
    public int value; 

    public MyValue(int i) { value = i; } 

    public String toString() { 
     return Integer.toString(value); 
    } 
} 

List<MyValue> l = new ArrayList<MyValue>(); 
l.add(new MyValue(10)); 
l.add(new MyValue(42)); 
System.out.println(l); 

List<MyValue> ul = Collections.unmodifiableList(l); 
ul.get(0).value = 33; 
System.out.println(l); 

uscita:

[10, 42] 
[33, 42] 

Ciò dimostra fondamentalmente è che se i dati contenuti nel List è mutabile in primo luogo, il contenuto della lista può essere modificato, anche se la lista stessa è immutabile.

+0

La mia lista conterrà sempre solo oggetti immutabili che estendono la classe Number (ad esempio intero, doppio, ecc.). –

+0

Ah, quindi questa è una cosa in meno di cui preoccuparsi :) – coobird

5

Ci sono un certo numero di cose per rendere la tua classe correttamente immutabile. Credo che questo sia discusso in Efficace Java.

Come menzionato in numerose altre risposte, per interrompere la modifica a list tramite l'iteratore restituito, Collections.unmodifiableList offre un'interfaccia di sola lettura. Se questa era una classe mutabile, potresti voler copiare i dati in modo che l'elenco restituito non cambi anche se questo oggetto lo fa.

L'elenco passato al costruttore può essere successivamente modificato, quindi è necessario copiarlo.

La classe è sottoclasse, quindi i metodi possono essere sovrascritti. Quindi rendi la classe final. Meglio fornire un metodo di creazione statica al posto del costruttore.

public final class Wrapper implements Iterable<Double> { 
    private final List<Double> list; 

    private Wrapper(List<Double> list) { 
     this.list = Collections.unmodifiableList(new ArrayList<Double>(list)); 
    } 

    public static Wrapper of(List<Double> list) { 
     return new Wrapper(list); 
    } 

    public Iterator<Double> iterator() { 
     return list.iterator(); 
    } 

    public List<Double> data() { 
     return list; 
    } 
} 

Anche evitare le schede e posizionare le parentesi nella posizione corretta per Java sarebbe utile.

+0

+1 per la raccomandazione di Java efficace. :) – cwash

+0

Non c'è davvero una "posizione corretta" per le parentesi graffe in Java più di quanto non ci sia in C. La posizione corretta per le parentesi graffe è la posizione che genera il minor numero di errori. –