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

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

  • автор:

Как создать свой immutable класс Java

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

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

  • 1. Шаги для создания immutable класса в Java: запретить расширение класса, сделать все поля final, не выставлять наружу методов-мутаторов.
  • 2. Чтобы создать неизменяемый класс в Java: не предоставляйте сеттеры или методы, которые изменяют поля или объекты, ссылающиеся на поля, сделайте все поля final и private, не разрешайте субклассам переопределять методы.
  • 3. Ключевое слово final используется для запрета изменения объектов в Java, для примитивов final делает постоянным присвоенное значение, для ссылки на объект — постоянной становится ссылка.
  • 4. Классы в Java, которые являются immutable: Integer, Byte, Character, Short, Boolean, Long, Double, Float.
  • 5. Immutable класс — это класс, который после инициализации не может изменить свое состояние, все изменения в нем приводят к созданию нового экземпляра.
  • 6. Для создания своего immutable класса в Java необходимо: сделать класс final, сделать все поля private, не создавать конструктор по умолчанию и сеттеры для полей, в конструкторе с параметрами и геттерах проводить глубокое копирование получаемого/передаваемого объекта.
  • 7. Для создания константы в Java необходимо использовать ключевое слово final, принято использовать большие буквы для констант.
  • 8. Для создания глубокой копии объекта в Java можно использовать переопределение метода clone() и реализацию интерфейса Cloneable(), а также использовать конструктор копирования.
  1. Как создать неизменяемый класс в Java
  2. Как сделать неизменяемый объект Java
  3. Какие классы в Java immutable
  4. Какие классы immutable Java
  5. Как создать свой immutable класс
  6. Как создать константу в Java
  7. Как создать глубокую копию объекта Java

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

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

1. Не предоставляйте сеттеры или методы, которые изменяют поля или объекты, ссылающиеся на поля.
2. Сделайте все поля final и private.
3. Не разрешайте субклассам переопределять методы.

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

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

Какие классы в Java immutable

Integer, Byte, Character, Short, Boolean, Long, Double, Float — все эти классы создают Immutable объекты.

Какие классы immutable Java

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

Как создать свой immutable класс

Чтобы создать immutable класс необходимо:

1. Сделать класс final.
2. Сделать все поля private.
3. Не создавать конструктор по умолчанию и сеттеры для полей
4. В конструкторе с параметрами и геттерах проводить глубокое копирование получаемого/передаваемого объекта.

Как создать константу в Java

Для того, чтобы переменную сделать константой, необходимо использовать ключевое слово final. 2. Константы в программе принято писать большими буквами, чтобы отличать их от обычных переменных.

Как создать глубокую копию объекта Java

Для клонирования объекта в Java можно пользоваться тремя способами:

1. Переопределение метода clone() и реализация интерфейса Cloneable();
2. Использование конструктора копирования;
3. Использовать для клонирования механизм сериализации

06.05.2023 Как создать свой immutable класс Java

В Java существуют классы, которые создают immutable (неизменяемые) объекты, такие как Integer, Byte, Character, Short, Boolean, Long, Double и Float. Однако, если вы желаете создать свой собственный immutable класс, то необходимо выполнить несколько шагов.

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

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

Кроме того, нельзя допускать существование сеттеров и методов, которые изменяют поля и объекты, ссылающиеся на поля.

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

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

Кроме того, в Java также можно создать константу, для этого нужно использовать ключевое слово final и присвоить значение переменной. Константы принято записывать большими буквами, чтобы отличать их от обычных переменных.

Для создания глубокой копии объекта Java существует несколько способов. Один из них — переопределение метода clone() и реализация интерфейса Cloneable(), а также использование конструктора копирования. При использовании последнего способа можно создать новый объект, копируя значения полей старого объекта в новый. Это обеспечит создание полностью независимой и глубокой копии объекта, которую можно использовать по своему усмотрению.

Таким образом, создание своего immutable класса в Java — это важный процесс, который позволит защитить ваш объект от ошибок, вызванных случайной мутацией состояния. Это поможет вам оптимизировать свой код и облегчить разработку приложения.

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

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

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

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

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

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

Неизменяемые объекты в Java

Неизменяемый объект — это объект, который не изменит своего внутреннего состояния после создания.

Неизменяемые объекты очень полезны в многопоточных приложениях, потому что они могут совместно использоваться потоками без синхронизации .

Неизменяемые объекты всегда безопасны для потоков.

Темы везде

Неважно, пишете вы явное многопоточное приложение или нет, часто вы работаете в многопоточной среде без непосредственного управления экземплярами потоков.

Вот несколько примеров многопоточных приложений, в которых программист не вызывает вручную создание новых потоков:

  • Веб-приложения, потому что сервлеты обрабатываются пулом потоков
  • Качайте настольные приложения, где потоки обрабатывают события GUI
  • Экземпляры таймера создают новые потоки для обработки будущих задач

Создание неизменного объекта

Чтобы создать неизменный объект, вам нужно следовать нескольким простым правилам:

  • Не добавляйте метод установки
  • Объявите все поля как окончательные и закрытые
  • Если поле является изменяемым объектом, создайте его защитные копии для методов получения
  • Если изменяемый объект, переданный конструктору, должен быть присвоен полю, создайте его защитную копию
  • Не позволяйте подклассам переопределять методы.

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

Не добавляйте метод установки

Если вы строите неизменный объект, его внутреннее состояние никогда не изменится. Задача метода-установщика состоит в том, чтобы изменить внутреннее значение поля, чтобы вы не могли его добавить.

Объявите все поля как окончательные и закрытые

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

Объявление поля final гарантирует, что если оно ссылается на примитивное значение, значение никогда не изменится, если оно ссылается на объект, ссылка не может быть изменена. Этого недостаточно, чтобы гарантировать, что объект с только частными конечными полями не является изменяемым. Вот пример, показывающий объект с приватным конечным полем и пример того, как изменить его внутреннее состояние:

public class DateContainer private final Date date;  public DateContainer() this.date = new Date(); >  public Date getDate() return date; > >  .   DateContainer dateContainer = new DateContainer(); System.out.println(dateContainer.getDate()); dateContainer.getDate().setTime(dateContainer.getDate().getTime() + 1000); System.out.println(dateContainer.getDate()); // Now dateContainer date is 1 second after

Если поле является изменяемым объектом, создайте его защитные копии для методов получения

Ранее мы видели, что определения поля final и private недостаточно, потому что можно изменить его внутреннее состояние. Чтобы решить эту проблему, нам нужно создать защитную копию этого поля и возвращать это поле каждый раз, когда оно запрашивается.

Вот предыдущий класс с этой модификацией:

public class DateContainer private final Date date;  public DateContainer() this.date = new Date(); >  public Date getDate() return new Date(date.getTime()); > >  .   DateContainer dateContainer = new DateContainer(); System.out.println(dateContainer.getDate()); dateContainer.getDate().setTime(dateContainer.getDate().getTime() + 1000); System.out.println(dateContainer.getDate()); // Now dateContainer date is not changed because we changed the copy, // not the original date

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

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

Здесь мы показываем модифицированный пример DateContainer, который принимает Date для конструктора, и мы увидим, как можно изменить его внутреннее состояние:

public class DateContainer private final Date date;  public DateContainer(Date date) this.date = date; >  public Date getDate() return new Date(date.getTime()); > >  .  Date date = new Date(); DateContainer dateContainer = new DateContainer(date); System.out.println(dateContainer.getDate()); date.setTime(date.getTime() + 1000); System.out.println(dateContainer.getDate()); // Now dateContainer date is 1 second after also if the getter method // create a defensive copy of date. We changed the reference passed to the // constructor, not the copy.

Таким образом, удерживая ссылку на объект, переданный конструктору, можно создавать изменяемые объекты. Для решения этой проблемы необходимо создать защитную копию параметра, если они являются изменяемыми объектами:

public class DateContainer private final Date date;  public DateContainer(Date date) this.date = new Date(date.getTime()); >  public Date getDate() return new Date(date.getTime()); > >  .  Date date = new Date(); DateContainer dateContainer = new DateContainer(date); System.out.println(dateContainer.getDate()); date.setTime(date.getTime() + 1000); System.out.println(dateContainer.getDate()); // Now dateContainer date is not changed. We create a copy on the constructor // so a change to the external date will not affect the internal state of // DateContainer instance

Обратите внимание, что если поле является ссылкой на неизменный объект, нет необходимости создавать его защитные копии в конструкторе и в методах получения, достаточно определить поле как окончательное и закрытое. В качестве примера общих неизменяемых объектов можно привести String, все примитивные оболочки (Integer, Long, Double, Byte ….), BigDecimal, BigInteger.

Не позволяйте подклассам переопределять методы

Если подкласс переопределяет метод, он может вернуть исходное значение изменяемого поля вместо его защитной копии.

Для решения этой проблемы можно выполнить одно из следующих действий:

  • Объявите неизменный класс как final, чтобы он не мог быть расширен
  • Объявите все методы неизменяемого класса final, чтобы они не могли быть переопределены
  • Создайте приватный конструктор и фабрику для создания экземпляров неизменяемого класса, потому что класс с приватными конструкторами не может быть расширен

Если вы будете следовать этим простым правилам, вы можете свободно делиться своими неизменяемыми объектами между потоками, потому что они потокобезопасны!

Java Core. Вопросы к собеседованию, ч. 1

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

Java Core. Вопросы к собеседованию, ч. 1 - 1

Для тех, кто впервые слышит слово Java Core — это фундаментальные основы языка. С этими знаниями уже смело можно идти на стажировку/интернатуру. Приведенные вопросы помогут вам освежить знания перед собеседованием, или почерпнуть для себя что-то новое. Для получения практических навыков занимайтесь на JavaRush.

  1. Как создать неизменяемый объект в Java? Перечислите все преимущества Неизменяемый класс – это класс, состояние которого не может быть изменено после создания. Здесь состоянием объекта по существу считаются значения, хранимые в экземпляре класса, будь то примитивные типы или ссылочные типы. Для того чтобы сделать класс неизменяемым, необходимо выполнить следующие условия:
    1. Не предоставляйте сеттеры или методы, которые изменяют поля или объекты, ссылающиеся на поля. Сеттеры подразумевают изменение состояния объекта а это то, чего мы хотим тут избежать.
    2. Сделайте все поля final и private . Поля, обозначенные private , будут недоступными снаружи класса, а обозначение их final гарантирует, что вы не измените их даже случайно.
    3. Не разрешайте субклассам переопределять методы. Самый простой способ это сделать – объявить класс как final . Финализированные классы в Java не могут быть переопределены.
    4. Всегда помните, что ваши экземпляры переменных могут быть либо изменяемыми, либо неизменяемыми. Определите их и возвращайте новые объекты со скопированным содержимым для всех изменяемых объектов (ссылочные типы). Неизменяемые переменные (примитивные типы) могут быть безопасно возвращены без дополнительных усилий.

    Также, вам необходимо помнить последующие преимущества неизменяемых классов. Возможно они понадобятся вас на собеседовании. Неизменяемые классы:

    • легко конструировать, тестировать и использовать
    • автоматически потокобезопасны и не имеют проблем синхронизации
    • не требуют конструктора копирования
    • позволяют выполнить «ленивую инициализацию» хэшкода и кэшировать возвращаемое значение
    • не требуют защищенного копирования, когда используются как поле
    • делают хорошие Map ключи и Set элементы (эти объекты не должны менять состояние, когда находятся в коллекции)
    • делают свой класс постоянным, единожды создав его, а он не нуждается в повторной проверке
    • всегда имеют «атомарность по отношению к сбою» (failure atomicity, термин применил Джошуа Блох): если неизменяемый объект бросает исключение, он никогда не останется в нежелательном или неопределенном состоянии.

    Посмотрите на пример, написанный в этом посте.

    Java Core. Вопросы к собеседованию, ч. 1 - 2

  2. В Java передача по значению или по ссылке? Java спецификация гласит, что все в Java передается по значению. Нет такого понятия, как «передача по ссылке» в Java. Эти условия связаны с вызовом методов и передачей переменных, как параметров метода. Хорошо, примитивные типы всегда передаются по значению без какой-либо путаницы. Но, концепция должна быть понятна в контексте параметра метода сложных типов. В Java, когда мы передает ссылку сложного типа как любой параметр метода, всегда адрес памяти копируется в новую ссылочную переменную шаг за шагом. Посмотрите на изображение: В приведенном примере, биты адреса первого экземпляра копируются другой ссылочной переменной, в результате чего обе ссылки указывают на один участок памяти, где хранится объект. Помните, что присвоив второй ссылке null, вы не присвоите null первой ссылке. Но изменение состояния объекта с одной ссылающейся переменной, будет отображено и в другой ссылке. Подробности смотрите тут.
  3. Какое применение блока finally ? Гарантирует этот блок выполнение своего кода? Когда finally блок не вызывается? Блок finally всегда вызывается, если блок try существует. Это гарантирует, что блок finally вызывается даже, если случается неожиданное исключение. Но finally является более полезным, чем просто для обработки исключений – этот блок позволяет выполнить чистку кода, случайно обошедшего через return , continue или break . Размещение очищающего кода в блок finally всегда является хорошей практикой, даже когда не ожидается никаких исключений. Если виртуальная машина завершает работу во время выполнения блока try или catch , тогда блок finally не будет выполнен. Аналогично, если нить, выполняя блок try или catch , будет прервана или убита, блок finally не будет выполнен, даже не смотря на то, что приложение продолжает работать.
  4. Почему существует два класса Date , один в java.util package а другой в java.sql ? java.util.Date представляет дату и время, а java.sql.Date представляет только дату. Дополнением к java.sql.Date является класс java.sql.Time , который представляет только время. Класс java.sql.Date является субклассом (расширением) класса java.util.Date . Итак, что изменилось в java.sql.Date :
    • toString() формирует другое представление строки: yyyy-mm-dd
    • статический метод valueOf(String) создает дату из строки с вышеуказанным представлением
    • исключены геттеры и сеттеры для часов, минут и секунд

    Класс java.sql.Date используется в JDBC и предназначен, чтобы не иметь составляющую времени, то есть часы, минуты, секунды и миллисекунды должны быть нулю… но это не является обязательным для класса.

    У нас в переводах уже есть хорошая статья о строках и строковом пуле: Часть 1, Часть 2.
     /** The value is used for character storage. */ /** Значение используется для хранения символов */ private final char value[]; Для доступа к этому массиву в различных сценариях используются следующие переменные /** The offset is the first index of the storage that is used. */ /** Смещение – это первый индекс используемого хранилища. */ private final int offset; /** The count is the number of characters in the String. */ /** Счет – это количество символов в строке. */ private final int count; 

    Каждый раз, когда мы создаем подстроку от существующего экземпляра строки, метод substring() только устанавливает новые значения переменных offset и count . Внутренний массив символов не изменяется. Это возможный источник утечки памяти, если метод substring() использовать неосторожно: Первоначальное значение value[] не изменяется. Поэтому если вы создадите строку длиной 10000 символов и создадите 100 подстрок с 5-10 символами в каждой, все 101 объекты будут содержать один и тот же символьный массив длиной 10000 символов. Это без сомнения расточительство памяти. Этого можно избежать, изменив код следующим образом: заменить original.substring(beginIndex) на new String(original.substring(beginIndex)) , где original – исходная строка.

    Примечание переводчика: я затрудняюсь сказать к какой версии Java это применимо, но на данный момент в Java 7 этот пункт статьи не актуален. Метод substring() вызывает конструктор класса new String(value, beginIndex, subLen) , который в свою очередь обращается к методу Arrays.copyOfRange(value, offset, offset+count) . Это значит, что у нас будет каждый раз новое значение переменной value[] , содержащее наше новое количество символов.
     static class Entry implements Map.Entry < final K key; V value; Entry next; final int hash; …//More code goes here >
    • В первую очередь, объект ключа проверяется на null . Если ключ null , значение сохраняется в позицию table[0] . Потому что хэшкод для null всегда 0.
    • Затем, следующим шагом вычисляется хэш значение вызывая у переменной-ключа свой метод hashCode() . Этот хэш используется для вычисления индекса в массиве для хранение объекта Entry . Разработчики JDK прекрасно понимали, что метод hashCode() может быть плохо написан и может возвращать очень большое или очень маленькое значение. Для решения этой проблемы они ввели другой hash() метод, и передают хэшкод объекта этому методу для приведения этого значения к диапазону размера индекса массива.
    • Теперь вызывается метод indexFor(hash, table.length) для вычисления точной позиции для хранения объекта Entry .
    • Теперь главная часть. Как мы знаем, два неодинаковых объекта могут иметь одинаковое значение хэшкода, как два разных объекта будет храниться в одинаковом расположении в архиве [называется корзиной]?

    Ответ – LinkedList . Если вы помните, класс Entry имеет свойство “next”. Это свойство всегда указывает на следующий объект в цепи. Такое поведение очень похоже на LinkedList .

    Итак, в случае совпадений хэшкодов, объекты Entry хранятся в форме LinkedList . Когда объект Entry необходимо разместить на конкретном индексе, HashMap проверяет, существует ли на этом месте другой объект Entry ? Если там нет записи, наш объект сохранится в этом месте.

    Если на нашем индексе уже находится другой объект, проверяется его поле next . Если оно равно null , наш объект становится следующим узлом в LinkedList . Если next не равно null , эта процедура повторяется, пока не будет найдено поле next равное null .

    Что будет, если мы добавим другое значение ключа, равное добавленному ранее? Логично, что оно должно заменить старое значение. Как это происходит? После определения индекса позиции для объекта Entry , пробегая по LinkedList , расположенному на нашем индексе, HashMap вызывает метод equals() для значения ключа для каждого объекта Entry . Все эти объекты Entry в LinkedList имеют одинаковое значение хэшкода, но метод equals() будет проверять на настоящее равенство. Если ключ. equals(k) будет true, тогда оба будут восприниматься как одинаковый объект. Это вызовет замену только объекта-значение внутри объекта Entry .

    Таким образом HashMap обеспечивает уникальность ключей.

    • В интерфейсах Java переменные априори final . Абстрактные классы могут содержать не final переменные.
    • Интерфейс в Java безоговорочно не может иметь реализации. Абстрактный класс может иметь экземпляры методов, которые реализуют базовое поведение.
    • Составляющие интерфейса должны быть public . Абстрактный класс может иметь модификаторы доступа на любой вкус.
    • Интерфейс должен быть реализован ключевым словом implements . Абстрактный класс должен быть расширен при помощи ключевого слова extends.
    • В Java класс может реализовывать множество интерфейсов, но может унаследоваться только от одного абстрактного класса.
    • Интерфейс полностью абстрактный и не может иметь экземпляров. Абстрактный класс также не может иметь экземпляров класса, но может быть вызван, если существует метод main() .
    • Абстрактный класс слегка быстрее интерфейса, потому что интерфейс предполагает поиск перед вызовом любого переопределенного метода в Java. В большинстве случаев это незначительное различие, но если вы пишите критичное по времени приложение, вам необходимо учесть и этот факт.

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

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