Неизменни обекти и немодифицируеми колекции

С неизменен обект, къде е правилно да се обвие съдържаща се колекция като непроменяема? Виждам 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. В конструктора на immutable

    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. В инструмента за достъп на immutable (ако е приложимо).

    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)


Бих казал, че ако създадете колекция Imutable, тогава трябва да се предпазите от някой, който променя списъка, който е даден като аргумент на конструктора, трябва да го копирате защитно.

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

Ще бъдете сигурни, че аргументът, който получавате, няма да бъде променен от никого.

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

Случай (1) по мое мнение има смисъл само когато фабричният метод е единственият начин за създаване на неизменна колекция и дори тогава може да се счупи, когато някой рефакторира тези класове. Освен ако няма други ограничения, винаги е най-добре да създавате самодостатъчни класове.

person Ivan Koblik    schedule 16.09.2012
comment
Страхотни точки. Ще опитам гуава, тъй като се фокусирах върху неизменността. - person John Ericksen; 17.09.2012
comment
ако имахте getter, който връща „стойностите“ на вашата колекция, бихте ли върнали тип List или ImmutableList? - person John Ericksen; 17.09.2012
comment
Просто бих върнал List. (Въпреки това Гуава е умен в това -- ако извикате 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