2009-06-11 13 views
5

Zaimplementowałem klasę w Javie, która wewnętrznie przechowuje listę . Chcę, aby klasa była niezmienna. Muszę jednak wykonywać operacje na wewnętrznych danych, które nie mają sensu w kontekście klasy. Dlatego mam inną klasę, która definiuje zestaw algorytmów. Oto uproszczony przykład:Niezmienne obiekty w Javie i dostęp do danych

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

Teraz moje pytanie brzmi, czy istnieje niezawodny sposób, aby uniemożliwić modyfikację moje wewnętrzne dane pomimo tego, że moja klasa ma być niezmienna? Chociaż udostępniłem metodę danych() tylko do odczytu, nic nie powstrzyma kogoś przed modyfikacją danych za pomocą metod takich jak clear() i remove(). Teraz zdaję sobie sprawę, że mogę zapewnić dostęp do moich danych wyłącznie za pośrednictwem iteratorów. Jednak powiedziano mi, że jest typowe, aby przekazać kolekcję . Po drugie, gdybym miał algorytm, który wymagałby więcej niż jednego przejścia przez dane, musiałbym podać wiele iteratorów, które wydają się śliskie.

Okay, mam nadzieję, że istnieje proste rozwiązanie, które rozwiąże moje obawy. Właśnie wracam do Javy i nigdy nie brałem pod uwagę tych rzeczy, zanim zajmiemy się const w C++. Z góry dziękuję!

Och! Jest jeszcze jedna rzecz, o której właśnie myślałem. Nie mogę praktycznie zwrócić kopii wewnętrznej listy . Lista zwykle zawiera setki tysięcy elementów.

Odpowiedz

23

Można użyć Collections.unmodifiableList i zmodyfikować metodę danych.

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

Z javadoc:

Zwraca niemodyfikowalne widok podanej listy. Ta metoda umożliwia modułom dostarczanie użytkownikom "tylko do odczytu" dostępu do list wewnętrznych. operacje zapytań na zwróconej listy „przeczytać” do podanej listy, i próbuje modyfikować zwracany listy, bezpośrednie lub za pośrednictwem iterator, skutkować UnsupportedOperationException.

+0

Idealnie! Nie widziałem tego. Dziękuję Ci. –

+1

Nadal musisz zająć się sposobem, w jaki tworzony jest obiekt Wrapper: ponieważ lista jest przekazywana do konstruktora, mogą istnieć odniesienia do niej gdzie indziej w kodzie. Jeśli chcesz, aby Wrapper był naprawdę niezmienny, musisz skopiować zawartość listy. –

5

Java nie ma koncepcji składniowej niezmiennej klasy. Od was, jako programisty, zależy dostęp do operacji, ale zawsze trzeba zakładać, że ktoś nadużywa tego.

Naprawdę niezmienny obiekt nie umożliwia ludziom zmiany stanu lub dostępu do zmiennych stanu, których można użyć do zmiany stanu. Twoja klasa nie jest niezmienna, jak jest teraz.

Jednym ze sposobów na niezmienność jest zwrócenie kopii wewnętrznej kolekcji. W takim przypadku należy bardzo dobrze ją udokumentować i ostrzec ludzi o użyciu jej w kodzie o wysokiej wydajności.

Inną opcją jest użycie kolekcji otoki, która wyświetlałaby wyjątki w czasie wykonywania, gdyby ktoś próbował zmienić wartość (niezalecane, ale możliwe, patrz przykładowy zbiór apache). Myślę, że biblioteka standardowa również je posiada (patrz klasa Kolekcje).

Trzecią opcją, jeśli niektórzy klienci zmieniają dane, a inni nie, jest zapewnienie różnych interfejsów dla klasy. Załóżmy, że masz IMyX i IMyImmutableX. Ta ostatnia definiuje jedynie "bezpieczne" operacje, podczas gdy pierwsza rozszerza ją i dodaje te niebezpieczne.

Oto kilka wskazówek dotyczących tworzenia niezmiennych klas. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

+0

Bardzo podoba mi się sugestia Alexa B. Jak wspomniałem w moim poście, nie mogę zwrócić kopii * Listy *. W swojej odpowiedzi wspominasz, że nie zaleca się zwracania niezmodyfikowanego widoku listy, jak zasugerował Alex. Czy mógłbyś to rozwinąć? –

+0

@Scott: W programowaniu jest ogólne podejście, które mówi "Nie zaskakuj swoich użytkowników" lub "preferuj błędy w czasie kompilacji do błędów w czasie wykonywania". Zazwyczaj lepiej jest, aby kompilator znalazł problem, a nie powodował awarię programu użytkownika w procesie produkcyjnym. Z listą niemodyfikowalną, zwrócisz poprawną listę, lista może zostać przekazana dookoła, aż znacznie później może nagle "wybuchnąć", gdy ktoś spróbuje ją zmodyfikować. – Uri

+0

@Scott: Jeśli potrzebujesz zwrócić listę i nie możesz jej skopiować, to kosztem, który musisz ponieść, jest ta wyjątek środowiska wykonawczego. Ale może rozważyć nazwanie swojej funkcji "getUnmodifiableList" lub coś podobnego. Moje badania wykazały, że większość ludzi nigdy nie czytałaby dokumentów o funkcjach, które wydają się intuicyjne, jak dane() – Uri

5

Czy można użyć Collections.unmodifiableList?

Zgodnie z dokumentacją zwróci on niemodyfikowalny (niezmienny) widok List. Zapobiegnie to użyciu metod takich, jak remove i add przez wyrzucenie UnsupportedOperationException.

Nie widzę jednak, że nie uniemożliwi to modyfikacji faktycznych elementów na samej liście, więc nie jestem do końca pewien, czy jest wystarczająco niezmienny. Przynajmniej sama lista nie może być modyfikowana.

Oto przykład, gdzie można jeszcze zmienione wartości wewnętrznej List zwróconej przez unmodifiableList:

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

wyjściowa:

[10, 42] 
[33, 42] 

Co to w zasadzie pokazuje, że jeżeli dane zawarte w List jest zmienne, zawartość listy można zmienić, nawet jeśli sama lista jest niezmienna.

+0

Moja lista będzie zawierać tylko obiekty trwałe, które rozszerzają klasę Number (np. Integer, Double, itd.). –

+0

Ah, to o jedno mniej martwić się :) – coobird

5

Jest kilka rzeczy, które sprawią, że Twoja klasa będzie niezmienna. Wierzę, że jest to omówione w Effective Java.

Jak wspomniano w wielu innych odpowiedziach, aby zatrzymać modyfikację na list za pośrednictwem zwróconego iteratora, Collections.unmodifiableList oferuje interfejs tylko do odczytu. Jeśli była to klasa podlegająca zmianie, możesz skopiować dane, aby zwrócona lista nie uległa zmianie, nawet jeśli ten obiekt działa.

Lista przekazana do konstruktora może później zostać zmodyfikowana, tak że należy ją skopiować.

Klasa jest podklasą, więc metody można przesłonić. Zrób więc klasę final. Lepiej zapewnić statyczną metodę tworzenia zamiast konstruktora.

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

Pomocne będzie również omijanie zakładek i umieszczanie nawiasów w poprawnej pozycji dla języka Java.

+0

+1 za rekomendowanie efektywnej Java. :) – cwash

+0

Nie ma tak naprawdę "prawidłowej pozycji" dla aparatów ortodontycznych w Javie więcej niż w C. Właściwa pozycja dla aparatów ortodontycznych to pozycja, która powoduje najmniej błędów. –

Powiązane problemy