Как сделать класс неизменяемым java
Перейти к содержимому

Как сделать класс неизменяемым java

  • автор:

Как написать иммутабельный класс?

Immutable (неизменяемый) класс – это класс, состояние экземпляров которого невозможно изменить после создания.

С иммутабельным классом всегда легче работать. Его состояние не поменяется, значит обращаться к нему в многопоточной среде можно без дополнительной синхронизации. Функции, зависящие только от состояния экземпляра будут возвращать один и тот же результат от вызова к вызову – это облегчает например реализацию hashCode(). Также вместо нескольких одинаковых экземпляров можно использовать один закэшированный объект, экономя память (паттерн Приспособленец).

Шаги, которые необходимо предпринять, чтобы класс стал immutable:

1. Запретите расширение класса – либо объявите его final , либо закройте доступ наследникам ко всем способам мутации, перечисленным в следующих пунктах;
2. Сделайте все поля финальными;
3. Не выставляйте наружу методов-мутаторов, которые меняют состояние;
4. Не отдавайте наружу поля ссылочного изменяемого типа (объекты классов, массивы) – если объект под ссылкой не иммутабельный, должна возвращаться его глубокая копия (defensive copy);
5. Создавайте объект правильно (подробнее в следующем посте).

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

Иммутабельность в Java

Привет, Хабр. В преддверии скорого старта курса «Подготовка к сертификации Oracle Java Programmer (OCAJP)» подготовили для вас традиционный перевод материала.

Приглашаем также всех желающих поучаствовать в открытом демо-уроке «Конструкторы и блоки инициализации». На этом бесплатном вебинаре мы:
— Разберём конструктор на запчасти
— Определим финалистов (финальные переменные)
— Наведём порядок (инициализации)

Иммутабельный (неизменяемый, immutable) класс — это класс, который после инициализации не может изменить свое состояние. То есть если в коде есть ссылка на экземпляр иммутабельного класса, то любые изменения в нем приводят к созданию нового экземпляра.

Чтобы класс был иммутабельным, он должен соответствовать следующим требованиям:

  • Должен быть объявлен как final, чтобы от него нельзя было наследоваться. Иначе дочерние классы могут нарушить иммутабельность.
  • Все поля класса должны быть приватными в соответствии с принципами инкапсуляции.
  • Для корректного создания экземпляра в нем должны быть параметризованные конструкторы, через которые осуществляется первоначальная инициализация полей класса.
  • Для исключения возможности изменения состояния после инстанцирования, в классе не должно быть сеттеров.
  • Для полей-коллекций необходимо делать глубокие копии, чтобы гарантировать их неизменность.

Иммутабельность в действии

Начнем со следующего класса, который, на первый взгляд, выглядит иммутабельным:

import java.util.Map; public final class MutableClass < private String field; private MapfieldMap; public MutableClass(String field, Map fieldMap) < this.field = field; this.fieldMap = fieldMap; >public String getField() < return field; >public Map getFieldMap() < return fieldMap; >>

Теперь посмотрим на него в действии.

import java.util.HashMap; import java.util.Map; public class App < public static void main(String[] args) < Mapmap = new HashMap<>(); map.put("key", "value"); // Инициализация нашего "иммутабельного" класса MutableClass mutable = new MutableClass("this is not immutable", map); // Можно легко добавлять элементы в map == изменение состояния mutable.getFieldMap().put("unwanted key", "another value"); mutable.getFieldMap().keySet().forEach(e -> System.out.println(e)); > > // Вывод в консоли unwanted key key

Очевидно, что мы хотим запретить добавление элементов в коллекцию, поскольку это изменение состояния объекта, то есть отсутствие иммутабельности.

import java.util.HashMap; import java.util.Map; public class AlmostMutableClass < private String field; private MapfieldMap; public AlmostMutableClass(String field, Map fieldMap) < this.field = field; this.fieldMap = fieldMap; >public String getField() < return field; >public Map getFieldMap() < MapdeepCopy = new HashMap(); for(String key : fieldMap.keySet()) < deepCopy.put(key, fieldMap.get(key)); >return deepCopy; > >

Здесь мы изменили метод getFieldMap , который теперь возвращает глубокую копию коллекции, ссылка на которую есть в AlmostMutableClass . Получается, что если мы получим Map , вызвав метод getFieldMap , и добавим к нему элемент, то на map из нашего класса это никак не повлияет. Изменится только map , которую мы получили.

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

import java.util.HashMap; import java.util.Map; public class App < public static void main(String[] args) < Mapmap = new HashMap<>(); map.put("good key", "value"); // Инициализация нашего "иммутабельного" класса AlmostMutableClass almostMutable = new AlmostMutableClass("this is not immutable", map); // Мы не можем изменять состояние объекта // через добавление элементов в полученную map System.out.println("Result after modifying the map after we get it from the object"); almostMutable.getFieldMap().put("bad key", "another value"); almostMutable.getFieldMap().keySet().forEach(e -> System.out.println(e)); System.out.println("Result of the object's map after modifying the initial map"); map.put("bad key", "another value"); almostMutable.getFieldMap().keySet().forEach(e -> System.out.println(e)); > > // Вывод в консоли Result after modifying the map after we get it from the object good key Result of the object's map after modifying the initial map good key bad key

Мы забыли, что в конструкторе нужно сделать то же самое, что и в методе getFieldMap . В итоге конструктор должен выглядеть так:

public AlmostMutableClass(String field, Map fieldMap) < this.field = field; MapdeepCopy = new HashMap(); for(String key : fieldMap.keySet()) < deepCopy.put(key, fieldMap.get(key)); >this.fieldMap = deepCopy; > // Вывод в консоли Result after modifying the map after we get it from the object good key Result of the object's map after modifying the initial map good key

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

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

Иммутабельность строк в Java

Класс String , представляющий набор символов, вероятно, самый популярный класс в Java. Его назначение — упростить работу со строками, предоставляя различные методы для их обработки.

Например, в классе String есть методы для получения символов, выделения подстрок, поиска, замены и многие другие. Как и другие классы-обертки в Java (Integer, Boolean и т.д.), класс String является иммутабельным.

Иммутабельность строк дает следующие преимущества:

  • Строки потокобезопасны.
  • Для строк можно использовать специальную область памяти, называемую «пул строк». Благодаря которой две разные переменные типа String с одинаковым значением будут указывать на одну и ту же область памяти.
  • Строки отличный кандидат для ключей в коллекциях, поскольку они не могут быть изменены по ошибке.
  • Класс String кэширует хэш-код, что улучшает производительность хеш-коллекций, использующих String .
  • Чувствительные данные, такие как имена пользователей и пароли, нельзя изменить по ошибке во время выполнения, даже при передаче ссылок на них между разными методами.

Как сделать иммутабельный класс java

«Иммутабельный класс» означает неизменяемый. Например, String является иммутабельным классом.

Для того чтобы класс считался иммутабельным — все его поля должны быть final . То есть любое изменение внутреннего состояния объекта, на основании иммутабельного класса, влечет за собой всегда создание нового объекта этого же класса.

Начиная с java 14 появился дополнительный синтаксис для создания неизменяемых объектов: java records .

Кофе-брейк #219. Как инициализировать ArrayList в Java. Как создать неизменяемый класс в Java

Кофе-брейк #219. Как инициализировать ArrayList в Java. Как создать неизменяемый класс в Java - 1

Источник: FreeCodeCamp Из этой статьи вы узнаете, как объявить и инициализировать ArrayList в Java. Также вы ознакомитесь с различными встроенными методами, которые можно использовать для добавления, доступа, изменения и удаления элементов в ArrayList. ArrayList — это реализация изменяемого массива интерфейса List , которая используется для хранения и управления коллекцией похожих переменных. ArrayList напоминает массив, но обеспечивает большую гибкость. Объект ArrayList более динамичен и дает вам широкий контроль над элементами в коллекции.

Как объявить ArrayList со значениями в Java

Объявить ArrayList в Java можно следующим способом:

 import java.util.ArrayList; public class ArrayListTut < public static void main(String[] args) < ArrayListpeople = new ArrayList<>(); > > 

Перед тем, как использовать ArrayList , вы должны сначала импортировать его из одноименного класса: import java.util.ArrayList; . После этого вы можете создать новый объект ArrayList . В приведенном выше коде мы создали такой объект под именем people . Обратите внимание, что тип данных ArrayList указывается в угловых скобках: ArrayList . Несмотря на то, что мы создали объект ArrayList , в нем пока нет элементов. Далее вы узнаете, как добавлять к нему элементы. Учтите, что вы можете создать ArrayList со значениями/элементами в точке объявления, используя метод add в блоке инициализатора:

 import java.util.ArrayList; public class ArrayListTut < public static void main(String[] args) < ArrayListpeople = new ArrayList<>() >; System.out.println(people); // [John, Jane, Doe] > > 

Как добавить элементы в ArrayList

Для добавления элементов в ArrayList необходимо использовать метод add() .

Java-университет

 import java.util.ArrayList; public class ArrayListTut < public static void main(String[] args) < ArrayListpeople = new ArrayList<>(); people.add("John"); people.add("Jane"); people.add("Doe"); System.out.println(people); // [John, Jane, Doe] > > 

В данном коде мы объявили ArrayList под названием people без каких-либо элементов. С помощью точки и метода add() мы добавили в people элементы: people.add(«John») .

Как получить доступ к элементам в ArrayList

Получить доступ к элементам в Java ArrayList можно, используя индекс элемента. Он будет передан в качестве параметра метода get() , примерно вот так:

 import java.util.ArrayList; public class ArrayListTut < public static void main(String[] args) < ArrayListpeople = new ArrayList<>(); people.add("John"); people.add("Jane"); people.add("Doe"); System.out.println(people.get(0)); // John > > 

В этом коде people.get(0) получает первый элемент — «John» . Обратите внимание, что первый элемент имеет индекс 0 , второй — индекс 1 и так далее.

Как изменить элементы в ArrayList

Вы можете изменить или модифицировать значение элемента в ArrayList с помощью метода set() . Метод set() принимает два параметра — индекс изменяемого элемента и новое значение, которое будет присвоено этому индексу. Перед вами пример:

 import java.util.ArrayList; public class ArrayListTut < public static void main(String[] args) < ArrayListpeople = new ArrayList<>(); people.add("John"); people.add("Jane"); people.add("Doe"); people.set(1, "Jade"); System.out.println(people); // [John, Jade, Doe] > > 

Здесь мы изменили второй элемент с «Jane» на «Jade» , используя его индекс: people.set(1, «Jade») .

Как удалить элементы в ArrayList

Вы можете удалить элемент, используя метод remove() . Этот метод принимает в качестве параметра индекс удаляемого элемента. Давайте взглянем на пример кода:

 import java.util.ArrayList; public class ArrayListTut < public static void main(String[] args) < ArrayListpeople = new ArrayList<>(); people.add("John"); people.add("Jane"); people.add("Doe"); people.remove(2); System.out.println(people); // [John, Jane] > > 

Используя метод remove() , мы удалили третий элемент в коллекции с помощью индекса элемента: people.remove(2); .

Заключение

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

Как создать неизменяемый класс в Java

  • Неизменяемый класс хорошо применять для кэширования, поскольку вам не нужно беспокоиться об изменении значения.
  • Неизменяемый класс по своей сути потокобезопасен, поэтому вам не нужно беспокоиться о безопасности потоков в многопоточных средах.
 String testImmutability = "String Not Immutable"; testImmutability.concat("---"); System.out.println(testImmutability); // Это будет успешно объединено, так как будет создан новый объект String String concat = testImmutability.concat("---"); System.out.println(concat); // Вывод String Not Immutable String Not Immutable--- 
  1. Объявите класс как final , чтобы его нельзя было расширить.
  2. Сделайте все поля private , чтобы прямой доступ был запрещен.
  3. Не предоставляйте методы setter для переменных.
  4. Сделайте все изменяемые поля final , чтобы значение поля можно было назначить только один раз.
  5. Инициализируйте все поля с помощью метода конструктора, выполняющего глубокое копирование.
  6. Выполните клонирование объектов в методах getter , чтобы возвращать копию, а не реальную ссылку на объект.
 final class ImmutableClassExample < private final MapdataMap; private final String name; private final String id; // Делаем глубокую копию в конструкторе public ImmutableClassExample(String name, String id, Map dataMap) < this.name = name; this.id = id; MaptempMap = new HashMap<>(); for (Map.Entry entry : dataMap.entrySet()) < tempMap.put(entry.getKey(), entry.getValue()); >this.dataMap = tempMap; > // Делаем глубокую копию в геттере public Map getDataMap() < MaptempMap = new HashMap<>(); for (Map.Entry entry : dataMap.entrySet()) < tempMap.put(entry.getKey(), entry.getValue()); >return tempMap; > public String getName() < return name; >public String getId() < return id; >> public class Test < public static void main(String args[]) < Mapmap = new HashMap<>(); map.put("1", "One"); map.put("2", "Two"); ImmutableClassExample s = new ImmutableClassExample("ABC", "101", map); System.out.println(s.getName()); System.out.println(s.getId()); System.out.println(s.getDataMap()); map.put("3", "third"); // Не изменится из-за глубокого копирования в конструкторе System.out.println(s.getDataMap()); s.getDataMap().put("4", "fourth"); // Раскомментирование следующего кода вызовет ошибку, так как мы пытаемся получить доступ к члену private // s.id = "4"; > > // Раскомментирование следующего кода вызовет ошибку, так как мы пытаемся наследовать класс final); > > */ /* class CheckInheritance extends ImmutableClassExample < public CheckInheritance(String name, String id, MapdataMap) < super(name, id, dataMap); >> */ 

Помните, что в коде не должно быть методов setter , чтобы не было возможности изменить данные.

Интересные факты

Метод создания неизменяемого класса в Java широко используется в версиях до Java 14. Проблема с приведенным выше решением заключается в том, что оно создает много шаблонного кода и допускает ошибки. В Java 14 появились записи (Records), которые можно использовать для создания неизменяемых объектов “только для чтения”. Записи представляют собой неизменяемую структуру данных, поэтому они более предпочтительны и должны использоваться вместо создания неизменяемых классов. Одним из важных аспектов записей является то, что поля final не могут быть перезаписаны с помощью отражения (reflection). Вот как выглядит приведенный ранее пример с использованием записей:

 record ImmutableClassExample(String name, String id, Map dataMap)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *