Зачем создавать защитные копии в геттерах внутри неизменяемых классов? - программирование
Подтвердить что ты не робот

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

Этот вопрос касается хороших методов программирования и предотвращения потенциальных явлений.
Я прочитал Joshua Bloch Эффективная Java и вот что мне интересно:
Почему я должен рассматривать создание защитных копий в методах геттера в моем неизменяемом классе без каких-либо мутаторов?
И второе: почему я должен делать свои поля окончательными в дополнение к частным? Это только о производительности (не безопасности)?

4b9b3361

Ответ 1

Я считаю, что это оправдывает эти утверждения:

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

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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

Пример кода для пояснения:

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

}

ИЗМЕНИТЬ

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

  • Клиентский код может изменить состояние параметра после создания вашего объекта. Чтобы избежать этой возможности, вам нужно сделать копию параметра. Например. подумайте, если конструктор объекта String String (char [] value) использовал массив char, который вы предоставляете, без его копирования: вы сможете изменить содержимое String измените массив char, который вы предоставили в конструкторе.

  • вы хотите быть уверены, что изменяемое состояние объекта не изменяется между временем, когда вы проверяете ограничение на него и время, когда вы копируете его в поле. Для этого reaon вам всегда нужно проверить ограничение на локальную копию параметра.

Ответ 5

Я слегка изменил ответ Томаша Нуркевича, чтобы продемонстрировать, почему окончание 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 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

Ответ 6

Почему я должен рассматривать создание защитных копий в методах getter в моем неизменяемом классе без каких-либо мутаторов в нем?

Это полезно, только если возвращаемые объекты уже не являются неизменяемыми.

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

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

Почему я должен делать свои поля окончательными в дополнение к приватным?

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

Ответ 7

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