Я не могу понять, в каких сценариях нам нужен неизменяемый класс.
Вы когда-нибудь сталкивались с подобным требованием? или не могли бы вы привести нам какой-нибудь реальный пример, где мы должны использовать этот шаблон.
java
design-patterns
immutability
Ракеш Жуял
источник
источник
Ответы:
Другие ответы кажутся слишком сосредоточенными на объяснении, почему неизменность - это хорошо. Это очень хорошо, и я использую его по возможности. Однако это не ваш вопрос . Я отвечу на ваш вопрос по пунктам, чтобы убедиться, что вы получаете ответы и примеры, которые вам нужны.
«Потребность» здесь относительный термин. Неизменяемые классы - это шаблон проектирования, который, как и любая парадигма / шаблон / инструмент, предназначен для упрощения создания программного обеспечения. Точно так же много кода было написано до появления парадигмы объектно-ориентированного программирования, но считайте меня одним из программистов, которым "нужен" объектно-ориентированный объект. Неизменяемые классы, такие как OO, не нужны строго , но я буду действовать так, как будто они мне нужны.
Если вы не смотрите на объекты в проблемной области с правильной точки зрения, вы можете не увидеть требования для неизменяемого объекта. Может быть легко подумать, что проблемная область не требует никаких неизменяемых классов, если вы не знаете, когда их использовать с пользой.
Я часто использую неизменяемые классы, когда думаю о данном объекте в моей проблемной области как о значении или фиксированном экземпляре . Это понятие иногда зависит от перспективы или точки зрения, но в идеале будет легко переключиться на правильную перспективу, чтобы определить хорошие объекты-кандидаты.
Вы можете лучше понять, где неизменяемые объекты действительно полезны (если не строго необходимы), прочитав различные книги / онлайн-статьи, чтобы развить хорошее представление о том, как думать о неизменяемых классах. Хорошая статья для начала - теория и практика Java: мутировать или не мутировать?
Я постараюсь привести ниже несколько примеров того, как можно видеть объекты в разных ракурсах (изменяемые и неизменные), чтобы прояснить, что я имею в виду под перспективой.
Поскольку вы просили реальных примеров, я дам вам несколько, но сначала давайте начнем с классических примеров.
Классические объекты-ценности
Строки и целые числа часто рассматриваются как значения. Поэтому неудивительно, что класс String и класс-оболочка Integer (а также другие классы-оболочки) неизменяемы в Java. Цвет обычно рассматривается как значение, отсюда неизменный класс Color.
Контрпример
Напротив, автомобиль обычно не рассматривается как объект ценности. Моделирование автомобиля обычно означает создание класса с изменяющимся состоянием (одометр, скорость, уровень топлива и т. Д.). Однако есть некоторые области, в которых car может быть объектом значения. Например, автомобиль (или, в частности, модель автомобиля) можно рассматривать как объект значения в приложении для поиска подходящего моторного масла для данного автомобиля.
Играя в карты
Вы когда-нибудь писали программу для игры в карты? Я сделал. Я мог бы представить игральную карту как изменяемый объект с изменяемой мастью и рангом. Рука с дро-покером может быть 5 фиксированных случаев, когда замена 5-й карты в моей руке будет означать преобразование 5-го экземпляра игральной карты в новую карту путем изменения ее масти и ранга ivars.
Однако я склонен думать об игральной карте как о неизменяемом объекте, который после создания имеет фиксированную неизменную масть и ранг. Моя комбинация дро-покера будет 5 экземпляров, и замена карты в моей руке потребует сброса одного из этих экземпляров и добавления нового случайного экземпляра в мою руку.
Проекция карты
Последний пример - когда я работал над кодом карты, где карта могла отображаться в различных проекциях . В исходном коде карта использовала фиксированный, но изменяемый экземпляр проекции (например, изменяемая игральная карта выше). Изменение проекции карты означало изменение ivars экземпляра проекции карты (тип проекции, центральная точка, масштабирование и т. Д.).
Однако я чувствовал, что дизайн был бы проще, если бы я думал о проекции как о неизменяемом значении или фиксированном экземпляре. Изменение проекции карты означало, что карта будет ссылаться на другой экземпляр проекции, а не изменять фиксированный экземпляр проекции карты. Это также упростило захват именованных проекций, таких как
MERCATOR_WORLD_VIEW
.источник
Неизменяемые классы в целом намного проще спроектировать, реализовать и правильно использовать. . Примером является String: реализация
java.lang.String
значительно проще, чемstd::string
в C ++, в основном из-за его неизменности.Одной конкретной областью, в которой неизменность имеет особенно большое значение, является параллелизм: неизменяемые объекты могут безопасно совместно использоваться несколькими потоками , тогда как изменяемые объекты должны быть поточно-безопасными за счет тщательного проектирования и реализации - обычно это далеко не тривиальная задача.
Обновление: эффективное Java 2nd Edition решает эту проблему подробно - см. Правило 15: Минимизируйте изменчивость .
См. Также эти связанные сообщения:
источник
Эффективная Java от Джошуа Блоха описывает несколько причин для написания неизменяемых классов:
В общем, рекомендуется делать объект неизменяемым, если в результате не возникают серьезные проблемы с производительностью. В таких обстоятельствах изменяемые объекты построителя могут использоваться для создания неизменяемых объектов, например StringBuilder.
источник
Хэш-карты - классический пример. Ключ к карте обязательно должен быть неизменным. Если ключ не является неизменяемым, и вы измените значение ключа так, что hashCode () приведет к новому значению, карта теперь повреждена (ключ теперь находится в неправильном месте в хеш-таблице).
источник
Java - это практически все без исключения ссылки. Иногда на экземпляр ссылаются несколько раз. Если вы измените такой экземпляр, он будет отражен во всех его ссылках. Иногда этого просто не нужно для повышения надежности и безопасности потоков. В этом случае полезен неизменяемый класс, так что вы будете вынуждены создать новый экземпляр и переназначить его текущей ссылке. Таким образом, исходный экземпляр других ссылок останется нетронутым.
Представьте, как бы выглядела Java, если бы она
String
была изменяемой.источник
Date
иCalendar
были изменчивыми. Ой, подождите, они такие, OH SHString
может изменяться! (подсказка: некоторые старые версии JRockit). Вызов string.trim () привел к обрезке исходной строкиПо сути, нам не нужны неизменяемые классы, но они определенно могут упростить некоторые задачи программирования, особенно когда задействовано несколько потоков. Вам не нужно выполнять какие-либо блокировки для доступа к неизменяемому объекту, и любые факты, которые вы уже установили о таком объекте, останутся верными в будущем.
источник
Возьмем крайний случай: целочисленные константы. Если я напишу такое утверждение, как «x = x + 1», я хочу быть на 100% уверенным, что число «1» каким-то образом не превратится в 2, что бы ни происходило где-либо еще в программе.
Хорошо, целочисленные константы - это не класс, но концепция та же. Допустим, я пишу:
Выглядит достаточно просто. Но если бы строки не были неизменяемыми, то мне пришлось бы рассмотреть возможность того, что getCustomerName может изменить customerId, так что когда я вызываю getCustomerBalance, я получаю баланс для другого клиента. Теперь вы можете спросить: «С какой стати кто-нибудь, пишущий функцию getCustomerName, заставит ее изменить идентификатор? В этом нет смысла». Но именно здесь у вас могут возникнуть проблемы. Человек, пишущий приведенный выше код, может счесть очевидным, что функции не изменят параметр. Затем приходит кто-то, кто должен изменить другое использование этой функции для обработки случая, когда у клиента есть несколько учетных записей под одним и тем же именем. И он говорит: «О, вот эта удобная функция имени getCustomer, которая уже ищет имя. Я»
Неизменяемость просто означает, что определенный класс объектов является константами, и мы можем рассматривать их как константы.
(Конечно, пользователь может присвоить переменной другой "постоянный объект". Кто-то может написать String s = "hello"; а затем написать s = "goodbye"; если я не сделаю переменную final, я не могу быть уверен что он не изменяется в моем собственном блоке кода. Точно так же, как целочисленные константы, уверяют меня, что «1» всегда одно и то же число, но не то, что «x = 1» никогда не будет изменено записью «x = 2». Но я может быть уверенным в том, что если у меня есть дескриптор неизменяемого объекта, что никакая функция, которой я передаю его, не может изменить его на мне, или что если я сделаю две его копии, что изменение переменной, содержащей одну копию, не изменит прочее.
источник
Есть разные причины неизменности:
String
класс.Итак, если вы хотите отправлять данные через сетевую службу и хотите иметь чувство гарантии , что ваш результат будет точно таким же, как и отправленный, установите его как неизменный.
источник
final
в Java, являются неизменяемыми, и не все неизменяемые классы отмеченыfinal
.Я собираюсь атаковать это с другой точки зрения. Я считаю, что неизменяемые объекты облегчают мне жизнь при чтении кода.
Если у меня есть изменяемый объект, я никогда не уверен, каково его значение, если он когда-либо использовался за пределами моей непосредственной области действия. Скажем, я создаю
MyMutableObject
локальные переменные метода, заполняю их значениями, а затем передаю их пяти другим методам. ЛЮБОЙ из этих методов может изменить состояние моего объекта, поэтому должно произойти одно из двух:Первое затрудняет рассуждение о моем коде. Второе снижает производительность моего кода - я в основном имитирую неизменяемый объект с семантикой копирования при записи, но делаю это постоянно, независимо от того, действительно ли вызываемые методы изменяют состояние моего объекта.
Если я вместо этого использую
MyImmutableObject
, я могу быть уверен, что значения, которые я установил, будут такими, какими они будут в течение всего срока службы моего метода. Нет никакого «жуткого действия на расстоянии», которое изменит его из-под меня, и мне не нужно делать защитные копии моего объекта перед вызовом пяти других методов. Если другие методы хотят изменить что-то для своих целей, они должны сделать копию, но они делают это только в том случае, если им действительно нужно сделать копию (в отличие от того, что я делал перед каждым вызовом внешнего метода). Я избавляю себя от мысленных ресурсов отслеживания методов, которых может даже не быть в моем текущем исходном файле, и избавляю систему от накладных расходов на бесконечное создание ненужных защитных копий на всякий случай.(Если я выйду за пределы мира Java и, скажем, в мир C ++, среди прочего, я могу стать еще сложнее. Я могу сделать объекты изменяемыми, но за кулисами сделать их прозрачным клонированием на любом своего рода изменение состояния - это копирование при записи - никто не станет мудрее.)
источник
Мои 2 цента будущим посетителям:
2 сценария, в которых неизменяемые объекты являются хорошим выбором:
В многопоточности
Проблемы параллелизма в многопоточной среде могут быть решены с помощью синхронизации, но синхронизация является дорогостоящим делом (не буду копаться здесь в «почему»), поэтому, если вы используете неизменяемые объекты, тогда нет синхронизации для решения проблемы параллелизма из-за состояния неизменяемые объекты не могут быть изменены, и если состояние не может быть изменено, все потоки могут беспрепятственно получить доступ к объекту. Таким образом, неизменяемые объекты - отличный выбор для общих объектов в многопоточной среде.
В качестве ключа для коллекций на основе хешей
Одна из наиболее важных вещей, на которые следует обратить внимание при работе с коллекцией на основе хэшей, заключается в том, что ключ должен быть таким,
hashCode()
чтобы он всегда возвращал одно и то же значение в течение всего времени жизни объекта, потому что если это значение изменяется, тогда старая запись сделана в коллекции на основе хеша использование этого объекта не может быть получено, следовательно, это может вызвать утечку памяти. Поскольку состояние неизменяемых объектов не может быть изменено, они являются отличным выбором в качестве ключа в коллекции на основе хэшей. Итак, если вы используете неизменяемый объект в качестве ключа для коллекции на основе хэша, вы можете быть уверены, что из-за этого не будет утечки памяти (конечно, все еще может быть утечка памяти, когда объект, используемый как ключ, не упоминается из любого места иначе, но дело не в этом).источник
Неизменяемые объекты - это экземпляры, состояния которых не меняются после инициации. Использование таких объектов зависит от требований.
Неизменяемый класс хорош для целей кеширования и является потокобезопасным.
источник
Благодаря неизменности вы можете быть уверены, что поведение / состояние базового неизменяемого объекта не изменится, при этом вы получите дополнительное преимущество выполнения дополнительных операций:
Вы можете легко использовать несколько ядер / процессоров ( параллельная / параллельная обработка ) (поскольку последовательность операций больше не имеет значения).
Может делать кеширование для дорогостоящих операций (так как вы уверены в том же
результате).
Может легко выполнять отладку (так как история выполнения больше не будет проблемой
)
источник
Использование ключевого слова final не обязательно делает что-то неизменным:
public class Scratchpad { public static void main(String[] args) throws Exception { SomeData sd = new SomeData("foo"); System.out.println(sd.data); //prints "foo" voodoo(sd, "data", "bar"); System.out.println(sd.data); //prints "bar" } private static void voodoo(Object obj, String fieldName, Object value) throws Exception { Field f = SomeData.class.getDeclaredField("data"); f.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); f.set(obj, "bar"); } } class SomeData { final String data; SomeData(String data) { this.data = data; } }
Это просто пример, демонстрирующий, что ключевое слово final используется для предотвращения ошибки программиста, и не более того. В то время как переназначение значения без ключевого слова final может легко произойти случайно, переход на эту длину для изменения значения должен быть сделан намеренно. Он предназначен для документации и предотвращения ошибок программиста.
источник
Неизменяемые структуры данных также могут помочь при кодировании рекурсивных алгоритмов. Например, предположим, что вы пытаетесь решить задачу 3SAT . Один из способов - сделать следующее:
Если у вас есть изменяемая структура для представления проблемы, то при упрощении экземпляра в ветви TRUE вам либо придется:
Однако, если вы умно закодируете его, у вас может быть неизменяемая структура, где любая операция возвращает обновленную (но все еще неизменяемую) версию проблемы (аналогично
String.replace
- она не заменяет строку, а просто дает вам новую ). Наивный способ реализовать это - сделать так, чтобы «неизменяемая» структура просто копировала и создавала новую при любой модификации, сводя ее ко второму решению при наличии изменяемого, со всеми этими накладными расходами, но вы можете сделать это более эффективный способ.источник
Одна из причин «потребности» в неизменяемых классах - комбинация передачи всего по ссылке и отсутствия поддержки представлений объекта только для чтения (то есть C ++
const
).Рассмотрим простой случай класса, поддерживающего шаблон наблюдателя:
class Person { public string getName() { ... } public void registerForNameChange(NameChangedObserver o) { ... } }
Если бы
string
они не были неизменяемыми, было бы невозможно правильноPerson
реализовать классregisterForNameChange()
, потому что кто-то мог бы написать следующее, эффективно изменяя имя человека, не вызывая никаких уведомлений.void foo(Person p) { p.getName().prepend("Mr. "); }
В C ++
getName()
возврат aconst std::string&
имеет эффект возврата по ссылке и предотвращения доступа к мутаторам, что означает, что неизменяемые классы в этом контексте не нужны.источник
Еще они дают нам гарантию. Гарантия неизменности означает, что мы можем расширять их и создавать новые шаблоны для повышения эффективности, которые иначе были бы невозможны.
http://en.wikipedia.org/wiki/Singleton_pattern
источник
Одна особенность неизменяемых классов, которая еще не была вызвана: хранение ссылки на глубоко неизменяемый объект класса является эффективным средством хранения всего содержащегося в нем состояния. Предположим, у меня есть изменяемый объект, который использует глубоко неизменяемый объект для хранения 50 КБ информации о состоянии. Предположим, далее, что я хочу 25 раз сделать «копию» моего исходного (изменяемого) объекта (например, для буфера «отмены»); состояние может меняться между операциями копирования, но обычно этого не происходит. Создание «копии» изменяемого объекта просто потребует копирования ссылки на его неизменяемое состояние, поэтому 20 копий будут просто равны 20 ссылкам. Напротив, если бы состояние содержалось в изменяемых объектах стоимостью 50К, каждая из 25 операций копирования должна была бы создавать свою собственную копию данных стоимостью 50К; хранение всех 25 копий потребовало бы хранения более одного мегабайта данных, в основном дублированных. Несмотря на то, что первая операция копирования создаст копию данных, которая никогда не изменится, а остальные 24 операции теоретически могут просто ссылаться на нее, в большинстве реализаций не было бы возможности для второго объекта, запрашивающего копию информация, чтобы знать, что неизменная копия уже существует (*).
(*) Один из шаблонов, который иногда может быть полезен, заключается в том, что изменяемые объекты имеют два поля для хранения своего состояния - одно в изменяемой форме и одно в неизменяемой форме. Объекты можно копировать как изменяемые или неизменяемые, и они начнут свою жизнь с того или иного набора ссылок. Как только объект хочет изменить свое состояние, он копирует неизменяемую ссылку на изменяемую (если это еще не было сделано) и аннулирует неизменяемую. Когда объект копируется как неизменяемый, если его неизменяемая ссылка не установлена, будет создана неизменная копия, и неизменная ссылка укажет на нее. Этот подход потребует несколько дополнительных операций копирования, чем "полноценная копия при записи" (например, запрос на копирование объекта, который был изменен с момента последней копии, потребует операции копирования,
источник
После создания экземпляра объекта его состояние не может быть изменено за время жизни. Что также делает его потокобезопасным.
Очевидно, String, Integer и BigDecimal и т. Д. После того, как эти значения созданы, их нельзя изменить в течение жизни.
источник
из эффективной Java; Неизменяемый класс - это просто класс, экземпляры которого нельзя изменить. Вся информация, содержащаяся в каждом экземпляре, предоставляется при его создании и фиксируется на время существования объекта. Библиотеки платформы Java содержат множество неизменяемых классов, в том числе String, упакованные примитивные классы, а также BigInteger и BigDecimal. Для этого есть много веских причин: неизменяемые классы легче разрабатывать, реализовывать и использовать, чем изменяемые классы. Они менее подвержены ошибкам и более безопасны.
источник