Возвращать копии личных данных, а не ссылок

В удивительной книге "Java Good Part" автор дает некоторый код, который возвращает копию объекта в его методе getter (используется с хорошо инкапсулированным полем), а затем заявляет:

Этот подход, который пытается вернуть копии личных данных а не ссылки на эти частные данные, как правило, хорошая идея

Почему это так? Я думал, что цель инкапсуляции состоит в том, чтобы убедиться, что никто не может фактически изменить частных членов. Так зачем писать что-то вроде этого

private someType fieldName = new someType(); 

...

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

someType getSomething() 
{
return new someType(fieldName); 
}

Из того, что я знаю сейчас, я имею в виду, прежде чем вы, ребята, поппите:

что имеет смысл до сих пор, что он обслуживает сбор мусора, поскольку этот подход не поддерживает ссылки на фактический объект.

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

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

Ответ 1

Когда типы изменяемы, часто полезно возвращать копию, чтобы клиент не мог кардинально изменить ваши данные, по крайней мере, не сообщая вам об этом. Рассмотрим:

public class Person {
    private Date dateOfBirth;

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        // Do some validation, e.g. that it after 1800
        this.dateOfBirth = dateOfBirth;
    }
}

Это выглядит хорошо, не так ли? Но как насчет:

Person person = new Person();
person.setDateOfBirth(new Date()); // Now... fine.
// Aha! Modify the Date to a very long time ago. Now anyone else
// using the Person will be messed up...
person.getDateOfBirth().setTime(Long.MIN_VALUE);

Если getDateOfBirth вместо этого возвращает копию, любые изменения, вызываемые вызывающим объектом для объекта Date, к которому относится возвращаемое значение, будут неактуальны никому другому. Объект Person остается в силе, поскольку он имеет действительную дату. Конечно, это должно быть документировано так, чтобы тот, кто написал этот код, ожидал, что он не повлияет на объект Person из-за возвращаемой копии.

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

Ответ 2

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

Если вы вызываете:

someType property = someObj.getSomething();

а затем

property.setSomeSubProperty(someValue);

Это изменит только копию someType, а не оригинал someType, сохраненный в someObj.

Если класс, содержащий метод getSomething(), является изменяемым, он может иметь метод setSomething(someType value), и использование этого метода будет приемлемым способом для изменения этого свойства.

Ответ 3

Хорошие ответы уже приходят, но позвольте мне придумать другие примеры и ссылки

Лучшим источником для объяснения будет Эффективная Java от Джоша Блоха. Есть как минимум две главы о неизменяемости и защитных копиях.

Чтобы сделать это коротко: В Java вы передаете все по ссылке (я знаю, что она упрощена, но это не так), и многие классы изменяемы. Следовательно, прямое назначение частного поля с внешним объектом не является действительно безопасным, поскольку значение под ним может меняться в любой момент времени из-за пределов объекта, нарушая инкапсуляцию.

Аксессуарные методы - суть разрушения инкапсуляции. В наиболее распространенной реализации вы просто публикуете поле и, как упоминалось выше, вы разрешаете кому-либо изменять базовые объекты, если они позволяют это делать. Лучший пример - коллекции ИМХО. Если вы вернете любую сборку Java по умолчанию, любой может добавить что-то к ней, удалить элемент или даже очистить его. Если ваша логика зависит от состояния или вы пишете многопоточное приложение, это самый простой способ получить состояние гонки, чего мы действительно не хотим.

Таким образом, хорошая практика - это

  • возвращает глубокую копию объекта (например, методы сбора копий Guava)
  • возврат представления к объекту (например, класс Collections и его методы)
  • с использованием неизменяемых объектов (проще всего их)
  • клонирование или другое напуганное дело.

У каждого из них есть своя стоимость, связанная с ними. Копирование/клонирование требует времени и памяти. Представления не полностью безопасны, поскольку базовая реализация может измениться в любой момент времени, неизменные объекты не позволяют изменять и их трудно реализовать в унаследованных системах и т.д. Вам нужно найти баланс, но мы всегда рады помочь: )

Последнее, это также хорошая практика, чтобы сделать защитную копию в конструкторе/настройщике переданного в изменяемом параметре по той же причине. Если бы кто-то добавил элементы в коллекцию, которые мы сделали final в конструкторе, это было бы довольно глупо, c мы не сохранили состояние, которое мы, очевидно, хотели. Таким образом, в конструкторе не просто простая инициализация, если вы не контролируете то, что было передано (и даже если вы это сделаете, это может быть хорошей идеей для копирования)

В качестве примера я предпочитаю коллекции, так как они легче рассуждать о том, как сделать копию/как они меняются, однако StringBuilder и Date, упомянутые в других ответах, действительно показывают, что это не только выпуск коллекций. Итак, лучший ответ: Помните, что последний ваш лучший друг. Используйте его часто, с самого начала, и никогда не доверяйте изменчивым незнакомцам!

Ответ 4

Поскольку нет таких вещей, как "личные данные", на самом деле есть только данные, которые вы не можете достичь, и данные, которые вы не можете изменить.

Предположим, что fieldName определяется как имя_StringBuilderName. Там нет ничего, что можно сделать для StringBuilder, который будет препятствовать тому, чтобы кто-то, у кого есть доступ к нему, изменил его. С другой стороны, если он определен как string fieldName, то (если отсутствует какое-то поистине злобное отражение), у него нет шансов изменить его.

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