Вопрос по java – Зачем делать защитные копии в геттерах внутри неизменных классов?

17

Этот вопрос касается хороших методов программирования и избежания потенциальных пробелов.
Я читаю «Эффективную Java» Джошуа Блоха и вот что мне интересно:
Почему я должен подумать о том, чтобы сделать защитные копии в методах получения в моем неизменном классе без мутаторов?
И второе: почему я должен сделать свои поляfinal в дополнение кprivate ? Это только о производительности (не безопасности)?

Я думал, что Иосуа сам объясняет это в книге, не так ли? Edwin Dalorzo
Неизменяемость в общем смысле отличается от неизменности в смысле JMM. Обещания JMM, которые имеют отношение к неизменным объектам (например, никогда не видимым в несогласованном состоянии, даже без синхронизации), применяются только к вещам, отмеченнымfinal. Kevin
Создание полейfinal действительно имеет некоторые преимущества в производительности, но на самом деле, вы выигрываете, усложняя случайную модификацию поля и нарушая неизменность. Louis Wasserman

Ваш Ответ

7   ответов
1

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

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

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

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

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

Некоторые примеры кода для пояснения:

public final class Question {      //final class to assure that inheritor could not
                                   // make it mutable

    private int textLenCache = -1;     //we alter it after creation, only if needed  
    private final String text;

    private Date createdOn;

    public Immutable(String text, Date createdOn) {
        Date copy=new Date(createdOn.getTime() ) //defensive copy on object creation

        //Ensure your class invariants, e.g. both params must be non null
        //note that you must check on defensive copied object, otherwise client 
        //could alter them after you check.
        if (text==null) throw new IllegalArgumentException("text");
        if (copy==null) throw new IllegalArgumentException("createdOn");

        this.text= text;  //no need to do defensive copy an immutable object
        this.createdOn= copy;
    }

    public int getTextLen() {  
         if (textLenCache == -1)
            textLenCache=text.length();   //client can't see our changed state, 
                                          //this is fine and your class is still 
                                          //immutable
         return textLenCache;    
    }

    public Date getCreatedOn() {
        return new Date(createdOn.getTime());         //Date is mutable, so defend!
    }

}

EDIT:

Защитная копия на изменяемых параметрах конструктора необходима по двум причинам:

client code could change the state of the parameter after your object is created. You need to make a copy of the parameter to avoid this possibility. E.g. think if String object constructor String(char[] value) had used the char array you provide without copy it: you will be able to alter the String content by alter, the char array you provide in constructor.

you want to be sure that the mutable object state does not change between the time when you check for constraint on it and the time you copy it i your field. For this reaon, you always have to check constraint on your local copy of the parameter.

Да, я добавляю правку в свой ответ.
Это хорошее дополнение к теме - нам нужно предотвратить изменчивость нашего объекта, предотвращая изменчивость параметров конструктора. Спасибо! iozee
Спасибо за комментарий. Не могли бы вы объяснить, почему я должен делать защитные копии в конструкторе? Почему я не могу просто проверить объект Date на равенство нулю? iozee
0

immutable class with no mutators in it?

This is only useful if the returned objects are not already immutable.

Deep Подобная неизменяемость полезна для предотвращения изменений в любых объектах, хранящихся в неизменяемом классе.

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

Why should I make my fields final in addition to private?

Просто чтобы помочь вам достичь неизменности и предотвратить случайное или произвольное изменение значений после их установки (например, путем создания подклассов).

39

public class Immutable {

    private final String name;

    private Date dateOfBirth;

    public Immutable(String name, Date dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }

    public String getName() {
        return name;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

}

getName() хорошо, так как он также возвращает неизменный объект. Тем не менееgetDateOfBirth() Метод может нарушить неизменность, потому что клиентский код может изменить возвращаемый объект, следовательно, изменяяImmutable объект также:

Immutable imm = new Immutable("John", new Date());

imm.getName(); //safe
Date dateOfBirth = imm.getDateOfBirth();
//hundreds of lines later
dateOfBirth.setTime(0);  //we just modified `imm` object

Можно возвращать неизменные объекты и примитивы (так как они возвращаются по значению). Однако вам нужно делать защитные копии изменяемых объектов, таких какDate:

public Date getDateOfBirth() {
    return new Date(dateOfBirth.getTime());
}

и обернуть коллекции в неизменяемые представления (если они изменчивы), например, увидетьCollections.unmodifiableList():

public List<Integer> getSomeIds() {
    return Collections.unmodifiableList(someIds);
}
@iozee: правильно, я добавлю это к своему ответу
Спасибо за ответ.public Date getDateOfBirth() { return new Date(dateOfBirth.getTime()); } Это правильный способ предотвратить это? iozee
Я был бы осторожен при переключении любого существующего кода на это, хотя - новый код не будет нулевым. Это означает, что если какое-либо из этих значений даты реально может быть "нулевым", вам придется добавить некоторые проверки в ваш метод получения.
Я не уверен, что он отвечает "почему я должен сделать свои поля окончательными в дополнение к частным"? ? другими словами, не будет ли более эффективно объявить dateOfBirth окончательным?
Разве вы не должны делать защитную копию по дороге в (в конструкторе)?
1

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

    public class Immutable {

        private final String name;
        private final Date dateOfBirth;

        public Immutable(String name, Date dateOfBirth) {
            this.name = name;
            this.dateOfBirth = dateOfBirth;
        }

        public String getName() { return name;  }

        public Date getDateOfBirth() { return dateOfBirth;  }

        public static void main(String[] args) {

            //create an object
            Immutable imm = new Immutable("John", new Date());

            System.out.println(imm.getName() + " " + imm.getDateOfBirth());

            //try and modify object's intenal
            String name = imm.getName(); //safe because Integer is immutable
            name = "George";             //has no effect on imm

            Date dateOfBirth = imm.getDateOfBirth();
            dateOfBirth.setTime(0);  //we just modified `imm` object

            System.out.println(imm.getName() + " " + imm.getDateOfBirth());
        }
    }

    **Output:** 
    John Wed Jan 13 11:23:49 IST 2016
    John Thu Jan 01 02:00:00 IST 1970
1

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

public class MyImmutable
{
  private final StringBuilder foo = new StringBuilder("bar");

  public StringBuilder getFoo()
  {
    return foo; // BAD!
  }
}

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

0

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

public class Immutable {
   private List<Object> something;

   public List<Object> getSomething(){
      return something; // everything goes for a toss, once caller has this, it can be changed
   }
}

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

Хорошо, но даже если это окончательно, я могу изменить то, что хочу в нем (вызвать любой метод). Поэтому я думаю, что не делает полеprivate ниfinal может гарантировать неизменность ссылочного объекта. iozee
3

если ссылка на объект передается методу, у этого метода не будет способа узнать, кто еще может иметь ссылку на этот же объект, или аналогично тому, что они могут попытаться сделать с ним. Точно так же, если метод возвращает ссылку на объект, невозможно сказать, что получатель мог бы сделать с ним. Следовательно, если тип позволяет кому-либо, имеющему ссылку, мутировать его, единственный способ, которым сущность, которая содержит ссылку на такой объект, может гарантировать, что он не будет мутирован, - это сохранить ссылку на частный объект, который снаружи никогда не имел и никогда не получит ссылку на.

Альтернатива стилю защитного копирования, показанному здесь (построение нового экземпляра объекта каждый раз, когда запрашивается информация), состоит в том, чтобы метод извлечения информации объекта принимал изменяемый объект и заполнял этот объект получаемой информацией. Этот подход требует, чтобы код, который запрашивает информацию, строил (вероятно, пустой) объект для принятия информации до вызова метода для ее извлечения, но если таковой будет, например. Если использовать цикл для извлечения, краткого изучения и отбрасывания 100 частей информации, может быть быстрее создать один объект, который можно повторно использовать каждый раз в цикле, чем создать 100 новых объектов, каждый из которых будет использоваться только кратко.

Спасибо за комментарий. Это как передать некоторый контейнерный объект методу и вернуть его с необходимыми данными в нем? Было бы здорово, если бы вы дали мне крошечный пример, просто чтобы понять. iozee
@iozee: Да. Вызывающая сторона предоставляет контейнер, в который должны идти данные, и вызываемая функция копирует их туда. Одна вещь, которая мне нравится в этом подходе, заключается в том, что он ясно дает понять, что изменения, внесенные в контейнер вызывающей стороны после возврата метода, не будут влиять на базовый источник данных. Напротив, если вызывающий объект возвращает объект, будет неясно, какое влияние окажут будущие изменения на этот объект на что-либо.

Похожие вопросы