2016-01-22 2 views
2

Например, у меня есть это:Можно ли рассматривать объект как неизменные, если документ его неизменность

public class Container{ 

    private final List<String> strs; 

    /** 
    * Contructs {@code Container} by a given {@code List} 
    * The content of the list referenced should not be modified 
    * after the constructor invocation. 
    */ 
    public Container(List<String> strs){ 
     this.strs = strs; 
    } 
    //Other staff 
} 

состояние Container может быть изменено после его строительства, но мы запрещаем в документации. Можно ли считать объект неизменным? Создание копии внутри конструктора нежелательно.

Неизменяемость предназначена для обеспечения безопасности потоков.

+1

Это не хорошо, чтобы полагаться на комментарии, чтобы направлять выбор программиста, потому что мы не всегда читать комментарии. Чаще всего мы полагаемся на пример кода и интуицию, чтобы решить, что можно сделать с объектом. –

+3

«Выполнение копирования внутри конструктора не является желательным». Ну, но если вы этого не сделаете, вы не можете претендовать на неизменность. – Thilo

+0

Лично я сделал бы копию в конструкторе. Это дает мне список, который я могу гарантировать, не будет добавлять, изменять порядок или терять элементы. Но если это должна быть глубокая копия или нет (т. Е. Если сами элементы должны быть неизменными) зависит от случая. – Thilo

ответ

2

Вы должны, как правило, не выставлять внутренние структуры классов снаружи. Это особенно важно для вторичных.

Поэтому я предлагаю сделать копию с new ArrayList(x) в вашем конструкторе.

Кроме того, вы можете использовать Collections.unmodifiableList() для предотвращения изменений внутри вашего класса.

Я предлагаю сочетание обоих, которое будет выглядеть следующим образом:

import java.util.Collections; 
... 

public Container(List<String> strs){ 
    this.strs = Collections.unmodifiableList(new ArrayList<>(strs)); 
} 

Ваш контейнер будет помнить членов списка в момент вызова конструктора. Если список изменен в другом месте, это не повлияет на ваш imutable.

Даже код в контейнере не сможет изменить список - вместо этого вместо этого будет выбрано значение UnsupportedOperationException.

полный, рабочий пример кода:

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 

public class X { 

    public static void main(String[] args) { 

     // create a list 
     List<String> myList = new ArrayList<>(); 
     myList.add("a"); 
     myList.add("b"); 

     // hand it over to the container 
     Container container = new Container(myList); 

     // modify it afterwards 
     myList.add("BUH!"); 

     // check contents of container 
     for (String item : container.strs) { 
      System.out.println(item); 
     } 
    } 

} 

class Container{ 

    final List<String> strs; 

    /** 
    * Contructs {@code Container} by a given {@code List} 
    * The content of the list referenced should not be modified 
    * after the constructor invokation. 
    */ 
    public Container(List<String> strs){ 
     this.strs = Collections.unmodifiableList(new ArrayList<>(strs)); 
    } 
    //Other staff 
} 

выводит:

a 
b 

(не выводит BUH!)

+0

Ну, если вы сделаете копию, вам больше не нужно делать 'unmodifiableList' (если вы не хотите защитить себя от себя). – Thilo

2

Вы можете просто документировать его для повышения производительности, но я d рекомендует только этот подход для непубличных API. В моей компании мы создали фреймворк и смогли снять некоторые из этих защит, чтобы повысить производительность/уменьшить использование памяти, но только в коде, который был внутренним. Мы решили предположить, что разработчики фреймворков не будут возиться, и это сработало хорошо.

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

1

В общем, нет. Неизменяемый объект можно безопасно распределять между потоками. Объект, безопасный для обмена между потоками, не является вопросом документации; его класс должен быть реализован определенным образом. Скажем, что «объекты этого класса являются неизменными» без реализации, которая сделало ее неизменной, будет активно вводить в заблуждение, неправду и должна рассматриваться как ошибка.

+2

На самом деле, класс выше был бы проблематичным даже с одним потоком (поскольку содержимое списка может измениться, что было бы странно, даже если это не приведет к самому «внутреннему противоречивому» списку), что может случиться с несколькими только потоки). – Thilo

3

Если вы не хотите копировать список, и все же хотите, чтобы он был действительно неизменным, вы можете использовать persistent data structure, например, в Clojure.

В Clojure все значения неизменяемы, поэтому вы всегда можете безопасно их перемещать.Когда кто-то добавляет элемент в начало списка, это логически создает новый список, но на самом деле он добавляет только один элемент, указывающий на заголовок старого списка, без необходимости копировать весь предыдущий список. Все структуры данных в Clojure подобны этим, и вы также можете использовать их в Java (см. Также what's a good persistent collections framework for use in java?).

Или вы можете использовать pcollections.

+1

Еще несколько вариантов выбора библиотеки обсуждались на http://stackoverflow.com/questions/7713274/java-immutable-collections – Thilo

2

Ваше решение разрешить такую ​​конструкцию представляет собой компромисс между гарантированной корректностью и производительностью.

Застройщик шаблон может дать вам третий способ справиться с этим (в зависимости, если это применимо к вашей конструкции списка):

построить Container создать Builder, который собирает элементы списка, а затем наконец, вызывает конструктор контейнера (private). Это позволяет удобно коллекции элементов списка и позволяет избежать копирования списка при создании окончательного объекта:

public class Container { 
    public static class Builder { 
     public Builder add(String s) { strs.add(s); return this; } 
     public Container create() { 
       Container c = new Container(Collections.unmodifiableList(strs)); 
       strs = null; 
       return c; 
     } 
     private List<String> strs = new ArrayList<>(); 
    } 

    private final List<String> strs; 

    private Container(List<String> strs){ 
      this.strs = strs; 
    } 
} 
+0

Обратите внимание, как он устанавливает 'strs = null' после создания экземпляра, так что вызывающий не может просто продолжать строить после список был использован в строительстве. Таким образом, он не будет больше изменен. (Призыв к unmodifiableList на самом деле не нужен, только защищает ваш контейнер от самого себя). – Thilo

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