2014-09-06 2 views
11

В удивительной книге «Хорошие детали Java» автор дает код, который возвращает копию объекта в его методе получения (для использования с хорошо инкапсулированным полем), а затем гласит:Обратные копии личных данных, а не ссылок

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

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

private someType fieldName = new someType(); 

... 

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

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

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

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

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

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

+2

Аспект сбора мусора является спорным. Я думаю: он может быстрее очистить эту конкретную ссылку, но это также приводит к тому, что GC происходит чаще. Основная цель, вероятно, неизменна. –

ответ

7

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

Если вы звоните:

someType property = someObj.getSomething(); 

, а затем

property.setSomeSubProperty(someValue); 

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

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

+0

Я вижу. В этом есть смысл. Но зачем мне это делать, пока поле уже закрыто? Для меня это выглядит как дополнительная работа. Это просто практика чистого кода? –

+2

@AndrewTobey Не имеет значения, является ли поле закрытым. Геттер является общедоступным, и если вы не возвращаете копию в получателе, он дает ссылку на поле, которое вы можете изменить. – Eran

+0

gotcha. Говорить о доступе в смысле модификаторов доступа означает прямое, тогда как методы означают косвенный доступ. Таким образом, в конце концов, дело в том, чтобы перейти без ссылки, которая предлагает косвенный доступ !? –

19

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

public class Person { 
    private Date dateOfBirth; 

    public Date getDateOfBirth() { 
     return dateOfBirth; 
    } 

    public void setDateOfBirth(Date dateOfBirth) { 
     // Do some validation, e.g. that it's 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); 

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

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

+1

Спасибо за примечание о неизменности. Я не обращал внимания на тот факт, что этот вопрос возникает только тогда, когда на вопрос об изменчивости отвечает «да». –

+1

Есть использование для возврата ссылки вместо копии, но учитывая, что это C#, для этого нужны свойства. 'person.DateOfBirth.setTime (...)' имеет больше смысла. –

+2

@Cole: Когда вы говорите «рассматривая это C#» - этот вопрос касается Java, а не C#. –

0

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

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

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

+0

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

+0

Многие люди неправильно понимают модификаторы - модификаторы должны помочь определить области, вызывающие озабоченность, а не безопасность и определенно не изменчивость. – jmoreno

2

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

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

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

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

Так хорошая практика либо

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

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

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

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

Смежные вопросы