2010-05-17 2 views
5

Вы часто читаете о неизменяемых объектах, требующих, чтобы конечные поля были неизменными в Java. Действительно ли это так, или просто достаточно, чтобы не иметь никакой общественной изменчивости и фактически не мутировать государство?Должны ли поля быть явно окончательными, чтобы иметь «правильный» неизменяемый объект?

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

Наличие окончательного поля дает очевидное преимущество предотвращения ошибок реализации (например, позволяет коду сохранять ссылку на строитель и «строить» объект несколько раз, в то же время мутируя существующий объект), но имея хранилище Builder его данные внутри объекта по мере его создания выглядят как DRYer.

Вопрос: Предположим, что Builder не пропустил объект раньше и перестает модифицировать объект после его создания (например, установив его ссылку на объект как нуль), на самом деле что-то получилось (например, улучшенная безопасность потоков) в «неизменности» объекта, если поля объекта были сделаны окончательными?

+1

Если я не ошибаюсь * final * также может помочь предотвратить «атаки отражения», когда программист-изгоев будет использовать отражение для доступа к полям вашего класса и изменения их содержимого. Был знаменитый пример, полностью извращающий класс * String * и показывающий, что во многих случаях * String * действительно может быть изменен (если не из-за неточности btw, а из-за того, что после базового * char [] * был доступен, если была «игра окончена», потому что вы не можете заставить неизменность по содержанию массива ... Но это не моя точка зрения: я хочу сказать, что отражение может помочь сделать действительно неприятные вещи). – TacticalCoder

+1

@user, перед лицом такого отражения вы не можете установить неизменность. Тем не менее, диспетчер безопасности работает над тем, чтобы сделать сторонний код таким же жизнеспособным. – Yishai

+0

wait ... Если у меня есть * final * класс, имеющий уникальный * final int *, вы можете изменить его с помощью отражения? Моя точка зрения заключалась именно в том, что * без * использования * final * вы не можете предотвратить атаки отражения (если у вас нет * Security Manager * на месте, но это почти никогда не бывает, поэтому я написал *) во многих случаях может быть изменено "* ...). Однако я не уверен, что вы можете использовать отражение, чтобы изменить * final int * ... Если вы не можете, то мой комментарий довольно по теме с вашим вопросом:) (Я сделал +1 ваш вопрос вчера кстати:) – TacticalCoder

ответ

6

Да, вы получаете «безопасность потока» от полей final. То есть значение, присвоенное полем final во время строительства, гарантируется, что оно будет видимым для всех потоков. Другой альтернативой безопасности потоков является объявление полей volatile, но тогда вы получаете высокие накладные расходы при каждом считывании & hellip; и смущает любого, кто смотрит на ваш класс, и задается вопросом, почему поля этого «неизменного» класса отмечены «неустойчивыми».

Маркировка полей final является наиболее правильной технически и наиболее четко отражает ваше намерение. К сожалению, это делает шаблон строителя очень громоздким. Я думаю, что должно быть возможно создать обработчик аннотации, чтобы синтезировать строитель для неизменяемого класса, как это делает Project Lombok с сеттерами и геттерами. Реальной работой будет поддержка IDE, необходимая для того, чтобы вы могли кодировать против строителей, которые на самом деле не существуют.

+0

Почему 'volatile' необходим? – curiousguy

+0

@curiousguy: 'volatile' гарантирует, что значение, записанное одним потоком, будет видимым другому. Без него читатель, например, может не очистить значение, кэшированное в регистре, и сделать чтение в основную память. – erickson

+0

См. Мой комментарий-ответ. – curiousguy

2

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

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

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

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

+1

«Объект с нефинальными частными полями, но не сеттеры, например, удовлетворяли бы этому требованию» - это само по себе не гарантирует неизменности. Getters может возвращать ссылки на элементы данных, и они могут быть изменены вызывающим абонентом. –

+0

@Eyal: true - хотя сами ссылки были бы неизменными. Если бы вы построили объект с неизменяемыми членами, то неизменность осталась бы полностью вниз. Альтернативно, создание неизменной композиции или коллекции изменчивых объектов имеет смысл в некоторых контекстах. – mikera

+0

Следует добавить: у разных людей есть несколько разные определения неизменяемости ... моя - «неизменность с точки зрения наблюдателя», которая, по моему мнению, является самой полезной, но я также слышал, как люди определяют неизменность, как абсолютно никакое изменение. – mikera

0

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

Можно было бы составить сценарий с нечеткой ситуацией, когда предположительно неизменяемый объект будет сбиваться с помощью атаки на POST-инъекцию из-за структуры привязки к веб-интерфейсу, которая настроена на использование рефлексии вместо bean-сеттеров.

0

У вас определенно может быть неизменяемый объект с не последними полями.

Например, см. Java 1.6 реализацию java.lang.String.

+0

@ user988052 Разве это не нарушает целостность класса и безопасность программ? – curiousguy

+0

@TacticalCoder - Даже окончательные поля могут быть изменены с помощью отражения. Таким образом, аргумент, что вы можете иметь «не-неизменяемые строки», просто неверен, если только вы также не захотите сказать, что невозможно создать неизменяемый объект в Java. Класс String по дизайну является неизменным. Отражение боковых шагов - это дизайн класса, и на самом деле это не актуально в контексте исходного вопроса или ответа плаката. – Scrubbie

+0

@TacticalCoder. Почему -1. Как вы думаете, String является изменяемым? Вы видели, что это реализация в 1.6? Как и в любой философии дизайна, вы доверяете своим субподрядчикам и самим себе управлять оборудованием и подблоками в соответствии с эксплуатационными характеристиками. Если вы начнете работать вне этих спецификаций (например, используя отражение на неизменяемых объектах), конечный результат может быть похож на Чернобыль -> http://en.wikipedia.org/wiki/Chernobyl_disaster. –

0

Комментарий: @erickson

Как что:

 
class X { volatile int i, j; } 
X y; 

// thread A: 
X x = new X; 
x.i = 1; 
x.j = 2; 
y = x; 

// thread B: 
if (y != null) { 
    a = y.i; 
    b = y.j; 
} 

?

+2

В Thread B не гарантировано видно, что 'y' назначен; даже если «y» может быть назначено », прежде чем« B »прочитает его, согласно часам на стене, нет барьера памяти, поэтому B может прочитать устаревшее значение« null »из' y'. Здесь, если вы удалили 'volatile' из' i' & 'j' и вместо этого положили его на' y', B увидит «текущие» значения для 'i',' j' и 'y'. Это связано с тем, что запись в поле volatile flushes записывает в любое поле в основную память, а чтение изменчивой переменной обновляет любые кешированные значения из основной памяти. – erickson

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

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