Неизменность лучше всего рассматривать с точки зрения пользователя API.Таким образом, ваш объектный API должен удовлетворять следующим двум условиям:
- У внешнего пользователя нет возможности изменить значение объекта
- Гарантия того, что пользователь в любое время читает или делаетиспользование значения объекта в будущем приведет к тому же результату
Важное примечание : на самом деле нормально иметь изменяемые данные внутри неизменяемого объекта , так какпока он ведет себя как неизменный объект с точки зрения пользователя API .Рассмотрим, например, java.lang.String: хотя он обычно считается окончательным неизменяемым классом, на самом деле он имеет изменяемое внутреннее поле для кэширования hashCode (не многие знают это!).
ответьте на свой вопрос: если вы хотите, чтобы в неизменяемом объекте содержался другой (изменяемый) объект, вам обычно нужно выполнить одно или несколько из следующих действий:
- Гарантия того, что никто другой не сможет изменить значение изменяемого объекта.Обычно это означает, что никто другой не может иметь ссылку на изменяемый объект, поэтому обычно это возможно только в том случае, если вы создаете объект самостоятельно, а не принимаете ссылку извне.
- Примите оборонительные мерыглубокая копия изменяемого объекта, и не раздавать ссылки на новую копию.Разрешить только те операции, которые читают новую копию в общедоступном API.Если вам нужно раздать ссылку на этот объект, вам нужно взять еще одну защитную копию (чтобы не раздавать ссылку на внутреннюю копию).
- Использовать неизменную оболочку дляизменчивый объект.Что-то вроде Collections.unmodifiableList .Это полезно, если вы хотите разослать ссылку на внутренний изменяемый объект, но не хотите подвергаться риску его изменения.
Все эти решения немного хакерские - лучшеОбщее решение заключается в том, чтобы избежать использования изменяемых объектов в неизменяемых объектах.В долгосрочной перспективе это вызывает проблемы, потому что рано или поздно изменчивая ссылка будет просочиться, и вам будет чрезвычайно трудно найти ошибку.Вам лучше перейти к полной иерархии неизменных объектов (подход, принятый такими языками, как Scala и Clojure)