Неизменяемый объект - это объект, который не изменит своего внутреннего состояния после создания.Они очень полезны в многопоточных приложениях, потому что они могут быть разделены между потоками без синхронизации.
Чтобы создать неизменный объект, вы должны следовать нескольким простым правилам:
1.Не добавляйте никакой метод установки
Если вы создаете неизменный объект, его внутреннее состояние никогда не изменится.Задача метода-установщика состоит в том, чтобы изменить внутреннее значение поля, поэтому вы не можете его добавить.
2.Объявите все поля final и private
Закрытое поле не видно снаружи класса, поэтому никакие ручные изменения не могут быть применены к нему.
Объявление поля final гарантирует, что если оно ссылается на примитивное значение, значение никогда не изменится, если оно ссылается на объект, ссылка не может быть изменена. Этого недостаточно, чтобы гарантировать, что объект только с частными конечными полями не является изменяемым.
3.Если поле является изменяемым объектом, создайте его защитные копии для методов получения
Ранее мы видели, что определения поля final и private недостаточно, поскольку можно изменить его внутреннее состояние. Чтобы решить эту проблему, нам нужно создать защитную копию этого поля и возвращать это поле каждый раз, когда оно запрашивается.
4.Если изменяемый объект, переданный конструктору, должен быть присвоен полю, создайте его защитную копию
Та же проблема возникает, если вы удерживаете ссылку, переданную в конструктор, поскольку ее можно изменить.Таким образом, удерживая ссылку на объект, переданный конструктору, можно создавать изменяемые объекты.Для решения этой проблемы необходимо создать защитную копию параметра, если они являются изменяемыми объектами.
Обратите внимание, что , если поле является ссылкой на неизменяемый объект, создавать его не нужноЗащитные копии этого в конструкторе и в методах получения достаточно, чтобы определить поле как окончательное и личное.
5.Не разрешайте подклассам переопределять методы
Если подкласс переопределяет метод, он может вернуть исходное значение изменяемого поля вместо его защитной копии.
Чтобы решить эту проблемуДля этого можно выполнить одно из следующих действий:
- Объявить неизменяемый класс как конечный, чтобы его нельзя было расширить
- Объявить все методы неизменяемого класса, чтобы они моглине переопределять
- Создать приватный конструктор и фабрику для создания экземпляров неизменяемого класса, потому что класс с приватными конструкторами не может быть расширен
Если вы следуете этим простым правиламвы можете свободно делиться своими неизменяемыми объектами между потоками, потому что они безопасны для потоков!
Ниже приведено несколько заметных моментов:
- Неизменяемые объекты действительно во многих случаях упрощают жизнь.Они особенно применимы к типам значений, где объекты не имеют идентификатора, поэтому их можно легко заменить, и они могут сделать параллельное программирование более безопасным и чистым (большинство пресловутых трудностей с обнаружением ошибок параллелизма в конечном счете вызвано изменяемымобщее состояние между потоками). Однако для больших и / или сложных объектов создание новой копии объекта для каждого отдельного изменения может быть очень дорогостоящим и / или утомительным .А для объектов с определенной идентичностью изменение существующих объектов намного проще и интуитивно понятнее, чем создание новой, измененной копии.
- Есть некоторые вещи, которые вы просто не можете сделать с неизменяемыми объектами, например, иметь двунаправленные отношения . Как только вы установите значение ассоциации для одного объекта, его идентичность изменится. Итак, вы устанавливаете новое значение для другого объекта, и оно также изменяется. Проблема в том, что ссылка на первый объект больше не действительна, поскольку создан новый экземпляр для представления объекта со ссылкой. Продолжение этого приведет к бесконечным регрессам.
- Чтобы реализовать бинарное дерево поиска , вы должны каждый раз возвращать новое дерево: вашему новому дереву придется делать копию каждого узла, который был изменен (неизмененные ветви общий). Для вашей функции вставки это не так уж плохо, но для меня все стало довольно неэффективно, когда я начал работать над удалением и перебалансировкой.
- Hibernate и JPA по сути диктуют, что ваша система использует изменяемые объекты, потому что вся предпосылка в том, что они обнаруживают и сохраняют изменения в ваших объектах данных.
- В зависимости от языка компилятор может выполнить кучу оптимизаций при работе с неизменяемыми данными, поскольку он знает, что данные никогда не изменятся. Все виды вещей пропускаются, что дает вам огромный выигрыш в производительности.
- Если вы посмотрите на другие известные языки JVM ( Scala, Clojure ), изменяемые объекты редко встречаются в коде, и поэтому люди начинают использовать их в сценариях, где однопоточности недостаточно.
Там нет правильного или неправильного, это просто зависит от того, что вы предпочитаете. Это зависит только от ваших предпочтений и от того, чего вы хотите достичь (а возможность легко использовать оба подхода без отчуждения преданных поклонников той или иной стороны - это священный грааль, к которому стремятся некоторые языки).