2009-06-11 6 views
5

Я реализовал класс на Java, который, внутри, хранит список . Я хочу, чтобы класс был неизменным. Однако мне нужно выполнить операции над внутренними данными, которые не имеют смысла в контексте класса. Следовательно, у меня есть другой класс, который определяет набор алгоритмов. Вот упрощенный пример:Неизменяемые объекты в 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; 
    } 
} 

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

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

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

ответ

23

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

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

Из Javadoc:

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

+0

Отлично! Я этого не видел. Спасибо. – 2009-06-11 03:09:26

+1

Вы по-прежнему должны заботиться о том, как создается объект Wrapper: поскольку List передается конструктору, в нем могут быть ссылки на него где-то еще в коде. Если вы хотите, чтобы Wrapper был действительно неизменным, вам нужно будет скопировать содержимое списка. –

5

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

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

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

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

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

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

+0

Мне очень нравится предложение Алекс Б. Как я упоминал в своем сообщении, я не могу вернуть копию * List *. В вашем ответе вы упомянули, что не рекомендуете возвращать неизменяемое представление списка, как предложил Алекс. Не могли бы вы рассказать? – 2009-06-11 03:11:46

+0

@Scott: В программировании есть общий подход, который гласит: «Не удивляйте своих пользователей», или «предпочитайте ошибки времени компиляции для ошибок времени выполнения». Как правило, предпочтительнее, чтобы компилятор обнаружил проблему, а не сбой в работе вашей программы пользователя. С неизменяемым списком вы возвращаете действительный список, тогда список может быть передан до тех пор, пока значительно позже он не сможет внезапно «взорваться», когда кто-то попытается его изменить. – Uri

+0

@Scott: Если вам нужно вернуть список и не можете его скопировать, тогда вам придется потратить на это исключение. Но, возможно, подумайте о том, чтобы назвать вашу функцию «getUnmodifiableList» или что-то в этом роде. Мои исследования показали, что большинство людей никогда бы не прочитало документы функций, которые кажутся интуитивными, например data() – Uri

5

Вы можете использовать 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 изменен в первую очередь, содержимое списка может быть изменено, даже если сам список является неизменным.

+0

. В моем списке будут храниться неизменяемые объекты, которые расширяют класс Number (например, Integer, Double и т. Д.). – 2009-06-11 03:18:17

+0

Ах, тогда об этом меньше всего беспокоиться :) – coobird

5

Есть целый ряд вещей, чтобы сделать ваш класс правильно неизменным. Я считаю, что это обсуждается в «Эффективной 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.

+0

+1 для рекомендации эффективной Java. :) – cwash

+0

На фигурных скобках в Java на самом деле нет «правильной позиции», чем в C. Правильное положение для фигурных скобок - это положение, которое приводит к наименьшим ошибкам. –

Смежные вопросы