Неизменяемые объекты и неизменяемые коллекции

С неизменяемым объектом, где уместно обернуть содержащуюся коллекцию как неизменяемую? Я вижу 3 варианта:

  1. На фабрике неизменяемых объектов:

    public class ImmutableFactory {
    
        public Immutable build(){
           List<Integer> values = new ArrayList<Integer>();
    
            values.add(1);
            values.add(2);
            values.add(3);
    
            return new Immutable(Collections.unmodifiableList(values), "hello");
        }
    }
    
  2. В неизменном конструкторе

    public class Immutable {
    
        private final List<Integer> values;
        private final String hello;
    
        public Immutable(List<Integer> values, String hello) {
            this.values = Collections.unmodifiableList(values);
           this.hello = hello;
        }
    
        public List<Integer> getValues() {
            return values;
        }
    
        public String getHello() {
            return hello;
        }
    }
    
  3. В неизменяемом методе доступа (если применимо).

    public class Immutable {
    
        private final List<Integer> values;
        private final String hello;
    
        public Immutable(List<Integer> values, String hello) {
            this.values = values;
            this.hello = hello;
        }
    
        public List<Integer> getValues() {
            return Collections.unmodifiableList(values);
        }
    
        public String getHello() {
            return hello;
        }
    }
    

Есть ли другие варианты и какой из них правильный?


person John Ericksen    schedule 16.09.2012    source источник


Ответы (2)


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

public Immutable(List<Integer> values, String hello) {
    this.values = Collections.unmodifiableList(new ArrayList<Integer>(values));
    this.hello = hello;
}

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

public Immutable(ImmutableList<Integer> values, String hello) {
    this.values = values;
    this.hello = hello;
}

Вы будете уверены, что аргумент, который вы получите, не будет никем изменен.

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

Случай (1), на мой взгляд, имеет смысл только тогда, когда фабричный метод — единственный способ создать неизменяемую коллекцию, и даже тогда он может сломаться, когда кто-то рефакторит эти классы. Если нет других ограничений, всегда лучше создавать самодостаточные классы.

person Ivan Koblik    schedule 16.09.2012
comment
Отличные очки. Я собираюсь попробовать гуаву, так как сосредоточился на неизменности. - person John Ericksen; 17.09.2012
comment
если бы у вас был геттер, который возвращает «значения» вашей коллекции, вы бы вернули тип List или ImmutableList? - person John Ericksen; 17.09.2012
comment
Я бы просто вернул List. (Тем не менее, Guava умна в этом отношении — если вы вызываете ImmutableList.copyOf для защиты списка, который уже неизменяем, он фактически не сделает копию, за исключением случаев, когда это вызовет утечку памяти.) - person Louis Wasserman; 17.09.2012
comment
@LouisWasserman Приятно слышать от члена команды Guava! Спасибо за такую ​​замечательную библиотеку! Из общего интереса, не могли бы вы указать мне страницу, где я мог бы прочитать о тех крайних случаях, которые могут привести к утечке памяти? - person Ivan Koblik; 17.09.2012
comment
Не совсем? В основном это просто очевидные случаи - например. если у вас есть hugeList.subList(0, 10), вы хотите явно скопировать его, потому что hugeList может содержать сотни тысяч элементов, которые вам не нужны. Просто такие глупые случаи. Вероятно, самое общее обсуждение того, что делает copyOf, можно найти здесь. - person Louis Wasserman; 17.09.2012

Во-первых, ваш код во всех местах действительно неизменяем, но только потому, что Integer неизменен.
Если бы у вас был private final List<SomeCustomClass> values;, Collections.unmodifiableList(values); не гарантировал бы, что отдельные SomeCustomClass в списке неизменны. Вам придется написать код для этого.
Сказав это, другой вариант — сделать защитную глубокую копию списка. Этот подход также предлагает неизменность.

person Cratylus    schedule 16.09.2012
comment
List<someClassType> не содержит элементы класса; он идентифицирует их. Если List<someClassType> сделать немодифицируемым, он всегда будет идентифицировать одни и те же элементы. Если у вас есть распечатанный список из десяти машин, которые оказались красными, покраска одной из машин в синий цвет не изменит список. В списке по-прежнему будут указаны те же десять автомобилей. Точно так же состояние List<someClassType> не инкапсулирует состояние элементов на нем, а просто их идентичность. Если кто-то держит список частных объектов, состояние которых его интересует, и хочет дать кому-то этот список... - person supercat; 17.09.2012
comment
... объектов, состояния которых представляют собой снимки объектов из первого списка, необходимо создать новые объекты как снимки объектов из первого списка и сгенерировать новый список, который идентифицирует эти новые объекты. Жаль, что ни Java, ни какие-либо языки .net не позволяют указать, что ссылка на объект хранится с целью инкапсуляции состояния частного изменяемого объекта. Тот факт, что не делается различия между переменными, которые инкапсулируют неразделяемое изменчивое состояние, идентичность, и то, и другое, или ни то, ни другое, является, на мой взгляд, скорее слабостью, чем силой. - person supercat; 17.09.2012