Зачем нам нужен неизменяемый класс?
Я не могу получить какие сценарии, где нам нужен неизменяемый класс.
Вы когда-нибудь сталкивались с таким требованием? или вы можете дать нам любой реальный пример, где мы должны использовать этот шаблон.
19 ответов:
другие ответы, похоже, сосредоточены на объяснении того, почему неизменность хороша. Это очень хорошо, и я использую его, когда это возможно. однако, это не твой вопрос. Я возьму ваш вопрос по пунктам, чтобы попытаться убедиться, что вы получаете ответы и примеры, которые вам нужны.
Я не могу получить какие сценарии, где нам нужен неизменяемый класс.
"потребность" - это относительный термин здесь. Неизменяемые классы являются шаблоном проектирования это, как и любая парадигма / шаблон / инструмент, должно облегчить создание программного обеспечения. Точно так же много кода было написано до того, как появилась парадигма OO, но посчитайте меня среди программистов, которые "нужно" ОО. Неизменяемые классы, такие как OO, не являются строго нужны, но я буду вести себя так, как будто они мне нужны.
вы когда-нибудь сталкивались с таким требованием?
Если вы не смотрите на объекты в проблемной области с помощью правильная перспектива, вы можете не увидеть требование для неизменяемого объекта. Может быть легко подумать, что проблемная область не требуются любые неизменяемые классы если вы не знаете, когда нужно использовать их выгодно.
Я часто использую неизменяемые классы, где я думаю о данном объекте в моей проблемной области как значение или фиксированный пример. Это понятие иногда зависит от точки зрения или точки зрения, но в идеале, это будет легко переключитесь в правильную перспективу, чтобы определить хорошие объекты-кандидаты.
вы можете получить лучшее представление о том, где неизменяемые объекты очень полезным (если это не совсем необходимо), убедившись, что Вы читаете различные книги/онлайн-статьи, чтобы развить хороший смысл о том, как думать о неизменяемых классах. Одна хорошая статья, чтобы вы начали это теория и практика Java: мутировать или не мутировать?
Я постараюсь привести пару примеров ниже о том, как можно видеть объекты в разных перспективах (изменчивые и неизменные), чтобы прояснить, что я имею в виду под перспективой.
... можете ли вы дать нам какой-либо реальный пример, где мы должны использовать этот шаблон.
Так как вы просили реальных примеров я дам вам некоторые, но сначала давайте начнем с некоторых классических примеров.
Classic Стоимость Объектов
строки и целые числа и часто рассматриваются как значения. Следовательно неудивительно, что класс String и класс-оболочка Integer (а также другие классы-оболочки) являются неизменяемыми в Java. Цвет обычно рассматривается как значение, таким образом, неизменяемый класс цвета.
контрпример
напротив, автомобиль обычно не рассматривается как объект ценности. Моделирование автомобиля обычно означает создание класса, который изменяет состояние (пробег, скорость, уровень топлива и т. д.). Однако есть некоторые домены, где это автомобиль может быть объектом значения. Например, автомобиль (или, в частности, модель автомобиля) можно рассматривать как объект значения в приложении для поиска надлежащего моторного масла для данного транспортного средства.
Игральные Карты
когда-нибудь писать программу игральных карт? Я сделал. Я мог бы представить игральную карту как изменяемый объект с изменяемой мастью и рангом. Ничья-покер рука может быть 5 фиксированных случаев, когда замена 5-й карты в моей руке будет означать мутацию 5-й игры экземпляр карты в новую карту, изменив ее масть и ранг Ивара.
тем не менее, я склонен думать о игральной карте как о неизменяемом объекте, который имеет фиксированную неизменяемую масть и ранг после создания. Моя рука draw poker будет иметь 5 экземпляров, и замена карты в моей руке будет включать в себя отбрасывание одного из этих экземпляров и добавление нового случайного экземпляра в мою руку.
Проекция
последний пример, когда я работал над некоторым кодом карты, где карта может отображаться в различных прогнозы. В исходном коде карта использовала фиксированный, но изменяемый экземпляр проекции (например, изменяемая игральная карта выше). Изменение проекции карты означало изменение ivars экземпляра проекции карты (Тип проекции, центральная точка, масштабирование и т. д.).
однако я чувствовал, что дизайн был проще, если я думал о проекции как о неизменяемом значении или фиксированном экземпляре. Изменение проекции карты означало наличие ссылки на карту a другой экземпляр проекции вместо изменения фиксированного экземпляра проекции карты. Это также упростило захват именованных проекций, таких как
MERCATOR_WORLD_VIEW
.
неизменяемые классы вообще много проще проектировать, реализовывать и правильно использовать. Примером является String: реализация
java.lang.String
значительно проще, чемstd::string
в C++, в основном из-за своей неизменности.одна конкретная область, где неизменность имеет особенно большое значение, - это параллелизм:неизменяемые объекты могут быть безопасно разделены между несколькими потоками, в то время как изменяемые объекты должны быть потокобезопасными через осторожны дизайн и реализация - обычно это далеко не тривиальная задача.
обновление:эффективная Java 2-е издание решает эту проблему подробно - см. пункт 15: минимизировать изменчивость.
Смотрите также статьи по теме:
эффективная Java Джошуа Блоха описывает несколько причин для написания неизменяемых классов:
- простота-каждый класс находится только в одном состоянии
- потокобезопасность-поскольку состояние не может быть изменено, синхронизация не требуется
- запись в неизменяемом стиле может привести к более надежному коду. Представьте себе, если бы строки не были неизменяемыми; любые методы getter, возвращающие строку, потребовали бы реализации для создания защитной копии перед строкой был возвращен в противном случае клиент может случайно или намеренно нарушить это состояние объекта.
В общем, это хорошая практика, чтобы сделать объект неизменным, если нет серьезных проблем с производительностью в результате. В таких условиях изменяемые объекты builder можно использовать для создания неизменяемых объектов, например StringBuilder
хэш-карты являются классическим примером. Крайне важно, чтобы ключ к карте был неизменным. Если ключ не является неизменяемым, и вы изменяете значение на ключе таким образом, что hashCode() приведет к новому значению, КАрта теперь сломана (ключ теперь находится в неправильном месте в хэш-таблице.).
Java-это практически одна и все ссылки. Иногда на экземпляр ссылаются несколько раз. Если вы измените такой экземпляр, он будет отражен во всех его ссылках. Иногда вы просто не хотите иметь это, чтобы улучшить надежность и безопасность нитей. Тогда неизменяемый класс полезен, так что он вынужден создать новая экземпляр и переназначить его на текущую ссылку. Таким образом, исходный экземпляр других ссылок остается нетронутый.
представьте, как будет выглядеть Java, если
String
был изменяемым.
мы не нужно неизменяемые классы, как таковые, но они, безусловно, могут облегчить некоторые задачи программирования, особенно когда задействовано несколько потоков. Вам не нужно выполнять блокировку для доступа к неизменяемому объекту, и любые факты, которые вы уже установили о таком объекте, будут оставаться верными в будущем.
существуют различные причины для неизменяемость:
- потокобезопасность: неизменяемые объекты не могут быть изменены, и его внутреннее состояние не может измениться, поэтому нет необходимости синхронизировать его.
- Это также гарантирует, что все, что я посылаю через (через сеть) должен прийти в том же состоянии, что и ранее отправлено. Это означает, что никто (подслушивающий) не может прийти и добавить случайные данные в мой неизменяемый набор.
- Это также проще для разработки. Вы гарантируете, что нет подклассы будут существовать, если объект является неизменяемым. Например, a
String
класса.Итак, если вы хотите отправить данные через сетевую службу, и вы хотите почувствовать гарантия что вы будете иметь ваш результат точно такой же, как то, что вы послали, установите его как неизменный.
возьмем крайний случай: целочисленные константы. Если я напишу такое утверждение, как" x=x+1", я хочу быть на 100% уверенным, что число" 1 " не станет каким-то образом 2, независимо от того, что происходит в другом месте программы.
теперь хорошо, целочисленные константы не являются классом, но концепция та же. Предположим, я напишу:
String customerId=getCustomerId(); String customerName=getCustomerName(customerId); String customerBalance=getCustomerBalance(customerid);
выглядит достаточно просто. Но если бы строки не были неизменяемыми, то мне пришлось бы рассмотреть возможность того, что getCustomerName может измениться customerId, так что когда я звоню getCustomerBalance, я получаю баланс для другого клиента. Теперь вы можете сказать: "зачем кому-то писать функцию getCustomerName, чтобы она изменила идентификатор? Это было бы бессмысленно.- Но именно там ты можешь попасть в беду. Человек, пишущий приведенный выше код, может принять это как просто очевидное, что функции не изменят параметр. Затем приходит кто-то, кто должен изменить другое использование этой функции для обработки дела где клиент имеет несколько учетных записей под одним именем. И он говорит: "о, вот эта удобная функция getCustomer name, которая уже ищет имя. Я просто автоматически изменю идентификатор на следующую учетную запись с тем же именем и помещу ее в цикл ...- А потом ваша программа начинает таинственно не работать. Это будет плохой стиль кодирования? Возможно. Но это именно проблема в тех случаях, когда побочный эффект не очевиден.
неизменяемость означает, что определенным классом объектов константы, и мы можем рассматривать их как константы.
(конечно, пользователь может назначить другой "постоянный объект" переменной. Кто-то может написать Строка s="привет"; а потом писать с=" "прощай""; Если я не сделаю переменную окончательной, я не могу быть уверен, что она не изменяется в моем собственном блоке кода. Точно так же, как целочисленные константы уверяют меня, что "1" всегда одно и то же число, но не то, что "x=1" никогда не будет изменено, написав "x=2". но я можно быть уверенным, что если у меня есть дескриптор к неизменяемому объекту, что никакая функция, которую я передаю, не может изменить его на меня, или что если я сделаю две его копии, что изменение переменной, содержащей одну копию, не изменит другую. Так далее.
Я собираюсь атаковать это с другой точки зрения. Я нахожу, что неизменяемые объекты облегчают мне жизнь при чтении кода.
Если у меня есть изменяемый объект, я никогда не уверен, что его значение, если он когда-либо используется за пределами моей непосредственной области. Допустим, я создаю
MyMutableObject
в локальных переменных метода заполните его значениями, А затем передайте его пяти другим методам. Любой из этих методов может изменить состояние моего объекта, поэтому одна из двух вещей должна происходят:
- Я должен отслеживать тела пяти дополнительных методов, думая о логике моего кода.
- Я должен сделать пять расточительных защитных копий моего объекта, чтобы гарантировать, что правильные значения передаются каждому методу.
первое затрудняет рассуждение о моем коде. Второй заставляет мой код сосать производительность - я в основном имитирую неизменяемый объект с семантикой копирования на запись в любом случае, но делаю это все время, действительно ли вызванные методы изменяют состояние моего объекта.
Если я вместо этого использую
MyImmutableObject
, Я могу быть уверен, что то, что я установил, это то, что значения будут для жизни моего метода. Нет никакого "жуткого действия на расстоянии", которое изменит его из-под меня, и мне не нужно делать защитные копии моего объекта перед вызовом пяти других методов. Если другие методы хотят изменить вещи для своих целей они надо сделать копию – но они делают это только в том случае, если им действительно нужно сделать копию (в отличие от того, что я делаю это перед каждым вызовом внешнего метода). Я избавляю себя от умственных ресурсов отслеживания методов, которые могут даже не быть в моем текущем исходном файле, и я избавляю систему от накладных расходов на бесконечное создание ненужных защитных копий на всякий случай.(Если я выйду за пределы мира Java и, скажем, в мир C++, среди прочих, я могу стать еще сложнее. Я могу заставить объекты выглядеть так если они изменчивы, но за кулисами делают их прозрачно клонированными при любом изменении состояния-это копирование на запись-и никто не будет мудрее.)
использование последнего ключевого слова не обязательно делает что-то неизменным:
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 " существует, чтобы предотвратить ошибку программиста, и не намного больше. В то время как переназначение значения без конечного ключевого слова может легко произойти случайно, переход на эту длину для изменения значения должен быть сделан намеренно. Это там для документации и для предотвращения ошибки программиста.
неизменяемые структуры данных также могут помочь при кодировании рекурсивных алгоритмов. Например, скажем, что вы пытаетесь решить a 3SAT проблема. Один из способов-сделать следующее:
- выберите неназначенную переменную.
- дайте ему значение TRUE. Упростите экземпляр, взяв предложения, которые теперь удовлетворены, и повторите, чтобы решить более простой экземпляр.
- если рекурсия в истинном случае не удалась, вместо этого назначьте эту переменную FALSE. Упростите этот новый экземпляр и повторите его решение.
Если у вас есть изменяемая структура для представления проблемы, то при упрощении экземпляра в истинной ветви вам либо придется:
- следите за всеми изменениями, которые вы делаете, и отменить их все, как только вы понимаете, что проблема не может быть решена. Это имеет большие накладные расходы, потому что ваша рекурсия может идти довольно глубоко, и это сложно кодировать.
- сделайте копию экземпляра, а затем измените копировать. Это будет медленно, потому что если ваша рекурсия составляет несколько десятков уровней, вам придется сделать много копий экземпляра.
однако если вы кодируете его умным способом, вы можете иметь неизменяемую структуру, где любая операция возвращает обновленную (но все еще неизменяемую) версию проблемы (аналогично
String.replace
- Он не заменяет строку, просто дает вам новый). Наивный способ реализовать это-просто скопировать "неизменяемую" структуру и сделать новую на любой модификации, уменьшая его до 2-го решения при наличии изменчивого, со всеми этими накладными расходами, но вы можете сделать это более эффективным способом.
одной из причин "необходимости" в неизменяемых классах является сочетание передачи всего по ссылке и отсутствия поддержки представлений объекта только для чтения (т. е. C++ ' S
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&
имеет эффект возврата по ссылке и предотвращения доступа к мутаторам, что означает, что неизменяемые классы не нужны в этом контексте.
Они также дают нам гарантии. Гарантия неизменности означает, что мы можем расширить их и создать новые скороговорки для эффективности, которые в противном случае невозможны.
одна особенность неизменяемых классов, которая еще не была вызвана: хранение ссылки на глубоко неизменяемый объект класса является эффективным средством хранения всего состояния, содержащегося в нем. Предположим, у меня есть изменяемый объект, который использует глубоко неизменяемый объект для хранения информации о состоянии на 50 тыс. Предположим далее, что я хочу в 25 случаях сделать " копию "моего исходного (изменяемого) объекта (например, для буфера" отменить"); состояние может меняться между операциями копирования, но обычно создание "копии" изменяемого объекта просто потребует копирования ссылки на его неизменяемое состояние, поэтому 20 копий будут просто составлять 20 ссылок. Напротив, если бы состояние содержалось в изменяемых объектах стоимостью 50 тыс., каждая из 25 операций копирования должна была бы производить свою собственную копию данных стоимостью 50 тыс.; хранение всех 25 копий потребовало бы хранения более мегабайта в основном дублированных данных. Даже если первая операция копирования создаст копию данных, которая никогда не будет изменение, а остальные 24 операции теоретически могут просто ссылаться на это, в большинстве реализаций не было бы способа для второго объекта, запрашивающего копию информации, узнать, что неизменяемая копия уже существует(*).
(*) один шаблон, который иногда может быть полезен для изменяемых объектов, имеет два поля для хранения их состояния-одно в изменяемой форме и одно в неизменяемой форме. Объекты могут быть скопированы как изменяемые или неизменяемые, и начнется жизнь с одного или другого эталонный набор. Как только объект хочет изменить свое состояние, он копирует неизменяемую ссылку на изменяемую (если это еще не было сделано) и аннулирует неизменяемую. Когда объект копируется как неизменяемый, если его неизменяемая ссылка не задана, будет создана неизменяемая копия, и неизменяемая ссылка указывает на это. Этот подход потребует несколько больше операций копирования, чем "полноценная копия при записи" (например, запрос на копирование объекта, который был мутирован поскольку последняя копия потребует операции копирования, даже если исходный объект никогда больше не мутирует), но это позволяет избежать сложностей с потоками, которые повлечет за собой FFCOW.
неизменяемые объекты-это экземпляры, состояния которых не изменяются после инициализации. Использование таких объектов является специфическим требованием.
неизменяемый класс-это хорошо для целей кэширования, и это является потокобезопасным.
из эффективной Java; Неизменяемый класс-это просто класс, экземпляры которого нельзя изменить. Все информация, содержащаяся в каждом экземпляре, предоставляется при его создании исправлено на время жизни объекта. Библиотеки платформы Java содержат много неизменяемые классы, включая String, коробочные примитивные классы и BigInte- гер и bigdecimal. Для этого есть много веских причин: неизменяемые классы легче проектировать, реализовывать и использовать, чем изменяемые классы. Они менее склонны к ошибке и более безопасны.
по неизменности вы можете быть уверены, что поведение не изменится, с этим вы получите дополнительное преимущество выполнения дополнительной операции:
вы можете использовать несколько ядер / обработки (параллельной обработки) С at легкость (так как последовательность больше не будет иметь значения.)
можно сделать кэширование для дорогостоящей операции(как вы уверены в том же
результат.)могу сделать отладка вольно(так как история бега не будет касаться
больше)
почему неизменяемый класс?
после создания экземпляра объекта его состояние не может быть изменено в течение жизни. Что также делает его потокобезопасным.
примеры :
очевидно, строка, целое число и bigdecimal и т. д. После создания эти величины нельзя изменить в жизни.
Use-case : После создания объекта подключения к базе данных с его значениями конфигурации может не потребоваться изменять его состояние где вы можете использовать неизменяемый класс
мои 2 цента для будущих посетителей:
2 сценарии, где неизменяемые объекты являются хорошим выбором являются:
в многопоточность
проблемы параллелизма в многопоточной среде могут быть очень хорошо решены синхронизацией, но синхронизация является дорогостоящим делом (не будет копать здесь "Почему"), поэтому, если вы используете неизменяемые объекты, то нет синхронизации для решения проблемы параллелизма, потому что состояние неизменяемого объекта объекты не могут быть изменены, и если состояние не может быть изменено, то все потоки могут беспрепятственно получить доступ к объекту. таким образом, неизменяемые объекты делают отличный выбор для общих объектов в многопоточной среде.
как ключ для хэш-коллекции на основе
одна из самых важных вещей, которые следует отметить при работе с коллекцией на основе хэша, заключается в том, что ключ должен быть таким, что его
hashCode()
всегда возвращает то же значение для жизни объект, потому что если это значение изменено, то старая запись, сделанная в коллекцию на основе хэша, используя этот объект, не может быть получена, следовательно, это вызовет утечку памяти. поскольку состояние неизменяемых объектов не может быть изменено, поэтому они делают большой выбор в качестве ключа в коллекции на основе хэша. Итак, если вы используете неизменяемый объект в качестве ключа для коллекции на основе хэша, вы можете быть уверены, что из-за этого не будет утечки памяти (конечно, может быть утечка памяти, когда объект, используемый в качестве ключа, нигде не упоминается, но это не главное здесь).