Неизменяемые объекты в Java и доступ к данным - PullRequest
5 голосов
/ 11 июня 2009

Я реализовал класс в Java, который внутренне хранит Список . Я хочу, чтобы класс был неизменным. Однако мне нужно выполнить операции с внутренними данными, которые не имеют смысла в контексте класса. Следовательно, у меня есть другой класс, который определяет набор алгоритмов. Вот упрощенный пример:

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

Теперь мой вопрос: есть ли надежный способ предотвратить изменение моих внутренних данных кем-либо, несмотря на то, что мой класс должен быть неизменным? Хотя я предоставил метод data () только для чтения, ничто не мешает кому-то изменять данные с помощью таких методов, как clear () и remove ( ) . Теперь я понимаю, что могу предоставить доступ к своим данным исключительно через итераторы. Тем не менее, мне сказали, что обычно передают Collection . Во-вторых, если бы у меня был алгоритм, который требовал более одного прохода данных, мне пришлось бы предоставить несколько итераторов, что похоже на скользкий уклон.

Хорошо, надеюсь, есть простое решение, которое решит мои проблемы. Я только возвращаюсь в Java и никогда не думал об этом до того, как занялся const в C ++. Заранее спасибо!

Oh! Есть еще одна вещь, о которой я только что подумал. Я практически не могу вернуть копию внутреннего списка . Список обычно содержит сотни тысяч элементов.

Ответы [ 4 ]

23 голосов
/ 11 июня 2009

Вы можете использовать Collections.unmodifiableList и изменить метод данных.

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

Из Javadoc:

Возвращает неизменяемое представление указанный список. Этот метод позволяет модули для предоставления пользователям доступ только для чтения к внутренним спискам. Операции запроса в возвращаемом списке «прочитать» указанный список, и пытается изменить возвращенный список, будь то прямой или через его итератор, результат в UnsupportedOperationException.

5 голосов
/ 11 июня 2009

Есть ряд вещей, которые сделают ваш класс правильно неизменным. Я считаю, что это обсуждается в эффективной Java.

Как уже упоминалось в ряде других ответов, чтобы остановить модификацию list через возвращенный итератор, Collections.unmodifiableList предлагает интерфейс только для чтения. Если это изменчивый класс, вы можете скопировать данные, чтобы возвращаемый список не изменился, даже если этот объект изменился.

Список, который передается конструктору, может быть позже изменен, поэтому его необходимо скопировать.

Класс подклассифицирован, поэтому методы могут быть переопределены. Так что сделай класс final. Лучше предоставить статический метод создания вместо конструктора.

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

Также было бы полезно избегать вкладок и ставить скобки в правильное положение для Java.

5 голосов
/ 11 июня 2009

Можете ли вы использовать Collections.unmodifiableList?

Согласно документации, он вернет неизменяемое (неизменяемое) представление List. Это предотвратит использование таких методов, как remove и add, бросая UnsupportedOperationException.

Однако я не вижу, чтобы это не помешало изменению реальных элементов в самом списке, поэтому я не совсем уверен, достаточно ли это неизменно. По крайней мере, сам список не может быть изменен.

Вот пример, в котором внутренние значения List, возвращаемые 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);

Выход:

[10, 42]
[33, 42]

В основном это показывает, что если данные, содержащиеся в List, в первую очередь являются изменяемыми, содержимое списка можно изменить, даже если сам список является неизменным.

5 голосов
/ 11 июня 2009

В Java нет синтаксической концепции неизменяемого класса. Вы, как программист, должны предоставить доступ к операциям, но вы всегда должны предполагать, что кто-то будет злоупотреблять этим.

Действительно неизменный объект не дает людям возможности изменить состояние или получить доступ к переменным состояния, которые можно использовать для изменения состояния. Ваш класс не является неизменным, как сейчас.

Один из способов сделать его неизменным - это вернуть копию внутренней коллекции, и в этом случае вы должны очень хорошо задокументировать ее и предупредить людей об использовании ее в высокопроизводительном коде.

Другим вариантом является использование коллекции-обертки, которая будет генерировать исключения во время выполнения, если кто-то попытается изменить значение (не рекомендуется, но возможно, см. Пример для apache-collection). Я думаю, что стандартная библиотека тоже есть (смотрите в классе Коллекции).

Третий вариант, если некоторые клиенты изменяют данные, а другие - нет, - предоставлять различные интерфейсы для вашего класса. Допустим, у вас есть IMyX и IMyImmutableX. Последний просто определяет «безопасные» операции, тогда как первый расширяет их и добавляет небезопасные.

Вот несколько советов по созданию неизменяемых классов. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

...