2009-06-11 16 views
5

Ich habe eine Klasse in Java implementiert, die intern eine Liste speichert. Ich möchte, dass die Klasse unveränderlich ist. Ich muss jedoch Operationen an den internen Daten durchführen, die im Kontext der Klasse nicht sinnvoll sind. Daher habe ich eine andere Klasse, die eine Reihe von Algorithmen definiert. Hier ist ein vereinfachtes Beispiel:unveränderliche Objekte in Java und Zugriff auf Daten

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; 
    } 
} 

Nun, meine Frage ist, gibt es eine zuverlässige Methode, um jemanden zu verhindern, Modifizieren meine internen Daten trotz der Tatsache, dass meine Klasse unveränderlich sein soll? die Daten über Methoden zu modifizieren, wie clear() und remove() Während ich ein Daten() Methode für Nur-Lese-Zwecke zur Verfügung gestellt haben, gibt es nichts, jemanden zu stoppen. Jetzt weiß ich, dass ich meine Daten ausschließlich über Iteratoren zugänglich machen kann. Allerdings wurde mir gesagt, dass es typisch ist, eine Collection zu bestehen. Zweitens, wenn ich einen Algorithmus hätte, der mehr als einen Durchlauf über die Daten erfordert, müsste ich mehrere Iteratoren bereitstellen, was wie ein rutschiger Abhang aussieht.

Okay, hoffentlich gibt es eine einfache Lösung, die meine Bedenken lösen wird. Ich komme gerade zurück in Java und habe nie über diese Dinge nachgedacht, bevor ich mich in C++ mit const beschäftigt habe. Danke im Voraus!

Oh! Da ist noch eine Sache, an die ich gerade gedacht habe. Ich kann praktisch keine Kopie der internen Liste zurückgeben. Die Liste enthält normalerweise Hunderttausende von Elementen.

Antwort

23

Sie können Collections.unmodifiableList verwenden und die Datenmethode ändern.

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

Vom javadoc:

Gibt eine unveränderbare Ansicht der angegebenen Liste. Diese Methode ermöglicht Module Benutzern "schreibgeschützten" Zugriff auf interne Listen zu ermöglichen. Abfrageoperationen auf der zurückgegebenen Liste „read through“ an der angegebenen Liste, und versucht, die Liste zurückgegeben zu verändern, sei es direkt oder über die Iterator, führen zu einer UnsupportedOperationException.

+0

Perfekt! Ich habe das nicht gesehen. Vielen Dank. –

+1

Sie müssen immer noch auf die Art und Weise achten, wie das Wrapper-Objekt erstellt wird: Da die Liste an den Konstruktor übergeben wird, kann es an anderer Stelle im Code Verweise darauf geben. Wenn Wrapper wirklich unveränderbar sein soll, müssen Sie den Inhalt der Liste kopieren. –

5

Java hat kein syntaktisches Konzept der unveränderlichen Klasse. Es liegt an Ihnen, als Programmierer, Zugriff auf Operationen zu geben, aber Sie müssen immer davon ausgehen, dass jemand es missbrauchen würde.

Ein wirklich unveränderliches Objekt bietet keine Möglichkeit für Personen, den Status zu ändern oder auf Zustandsvariablen zuzugreifen, die zum Ändern des Status verwendet werden können. Deine Klasse ist nicht unveränderlich wie es jetzt ist.

Ein Weg, um es unveränderlich zu machen, ist eine Kopie der internen Sammlung zurückzugeben, in diesem Fall sollten Sie es sehr gut dokumentieren und Leute davon warnen, es in Hochleistungscode zu verwenden. Eine andere Option besteht darin, eine Wrapper-Sammlung zu verwenden, die zur Laufzeit Ausnahmen auslöst, wenn jemand versucht, den Wert zu ändern (nicht empfohlen, aber möglich, siehe beispielsweise apache-collections). Ich denke, die Standardbibliothek hat auch einen (siehe unter der Klasse Collections).

Eine dritte Option, wenn einige Clients Daten ändern, andere nicht, besteht darin, unterschiedliche Schnittstellen für Ihre Klasse bereitzustellen. Nehmen wir an, Sie haben ein IMyX und IMyImmutableX. Letzteres definiert nur die "sicheren" Operationen, während der erstere es erweitert und die unsicheren hinzufügt.

Hier sind einige Tipps, wie Sie unveränderliche Klassen erstellen können. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

+0

Ich mag Alex B's Vorschlag wirklich. Wie ich in meinem Beitrag erwähnt habe, kann ich keine Kopie der * Liste * zurückgeben. In Ihrer Antwort erwähnen Sie, dass Sie nicht empfehlen, eine unmodifizierbare Ansicht der Liste zurückzugeben, wie Alex vorgeschlagen hat. Kannst du bitte etwas ausarbeiten? –

+0

@Scott: Es gibt einen allgemeinen Ansatz in der Programmierung, der sagt: "Überraschen Sie Ihre Benutzer nicht" oder "bevorzugen Sie Kompilierzeitfehler zu Laufzeitfehlern". Es ist im Allgemeinen vorzuziehen, dass der Compiler ein Problem findet, anstatt dass das Programm des Benutzers in der Produktion zum Absturz gebracht wird. Mit einer nicht änderbaren Liste geben Sie eine gültige Liste zurück, die Liste kann dann weitergegeben werden, bis viel später sie "explodieren" kann, wenn jemand versucht, sie zu ändern. – Uri

+0

@Scott: Wenn Sie eine Liste zurückgeben müssen und sie nicht kopieren können, müssen Sie diese Laufzeitausnahmesache in Kauf nehmen. Aber vielleicht erwägen Sie, Ihre Funktion "getUnmodiableList" oder etwas Ähnliches zu benennen. Meine Recherchen zeigten, dass die meisten Menschen niemals die Dokumente von Funktionen lesen würden, die intuitiv scheinen, wie data() – Uri

5

Können Sie Collections.unmodifiableList verwenden?

Gemäß der Dokumentation wird eine unveränderbare (unveränderliche) Ansicht der List zurückgegeben. Dies verhindert die Verwendung von Methoden wie remove und add durch Werfen einer UnsupportedOperationException.

Allerdings sehe ich nicht, dass es nicht die Änderung der tatsächlichen Elemente in der Liste selbst verhindert, also bin ich nicht ganz sicher, ob das unveränderlich genug ist. Zumindest die Liste selbst kann nicht geändert werden.

Hier ist ein Beispiel, bei dem die inneren Werte der List zurück durch unmodifiableList noch verändert werden können:

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); 

Ausgang:

[10, 42] 
[33, 42] 

Was dies im Grunde zeigt, dass, wenn die Daten in dem enthaltenen List ist in erster Linie veränderbar, der Inhalt der Liste kann geändert werden, auch wenn die Liste selbst unveränderlich ist.

+0

Meine Liste enthält immer nur unveränderliche Objekte, die die Number-Klasse erweitern (z. B. Integer, Double usw.). –

+0

Ah, dann ist das eine Sache, über die man sich keine Gedanken machen muss :) – coobird

5

Es gibt eine Reihe von Dingen, um Ihre Klasse richtig unveränderlich zu machen. Ich glaube, das wird in Effective Java diskutiert.

Wie in einer Reihe anderer Antworten erwähnt, bietet Collections.unmodifiableList eine schreibgeschützte Schnittstelle, um die Änderung an list über den zurückgegebenen Iterator zu stoppen. Wenn dies eine änderbare Klasse ist, möchten Sie möglicherweise die Daten kopieren, sodass die zurückgegebene Liste nicht geändert wird, selbst wenn dieses Objekt vorhanden ist.

Die Liste, die an den Konstruktor übergeben wird, kann später geändert werden, sodass sie kopiert werden muss.

Die Klasse ist unterklassierbar, daher können Methoden überschrieben werden. Also mach die Klasse final. Stellen Sie besser eine statische Erstellungsmethode anstelle des Konstruktors bereit.

Auch die Vermeidung von Tabs und das Setzen von Klammern in die richtige Position für Java wäre hilfreich.

+0

+1 für die Empfehlung von Effective Java. :) – cwash

+0

Es gibt nicht wirklich eine "korrekte Position" für geschweifte Klammern in Java, genauso wenig wie in C. Die korrekte Position für geschweifte Klammern ist die Position, die zu den wenigsten Bugs führt. –

Verwandte Themen