Введение
Эта статья предоставляет обзор того, как создать неизменяемый класс в программировании на языке Java.
Объект является неизменяемым, когда его состояние не изменяется после инициализации. Например, String
– неизменяемый класс, и, после создания экземпляра, значение объекта String
никогда не изменится. Узнайте больше о почему класс String
неизменяем в Java.
Поскольку неизменяемый объект не может быть обновлен, программы должны создавать новый объект для каждого изменения состояния. Однако у неизменяемых объектов также есть следующие преимущества:
- Неизменяемый класс хорошо подходит для кэширования, потому что вам не нужно беспокоиться о изменениях значения.
- Неизменяемый класс по своей сути потокобезопасен, поэтому вам не нужно беспокоиться о безопасности потоков в многопоточных средах.
Узнайте больше о многопоточности в Java и просмотрите вопросы с собеседований по многопоточности в Java.
Создание неизменяемого класса в Java
Чтобы создать неизменяемый класс в Java, вам нужно следовать этим общим принципам:
- Объявите класс как
final
, чтобы его нельзя было расширить. - Сделайте все поля
private
, чтобы не разрешать прямой доступ. - Не предоставляйте методы-установщики для переменных.
- Сделайте все изменяемые поля
final
, чтобы значение поля можно было присвоить только один раз. - Инициализируйте все поля с помощью метода конструктора, выполняющего глубокое копирование.
- Выполняйте клонирование объектов в методах получения, чтобы возвращать копию вместо фактической ссылки на объект.
Ниже приведен пример класса, иллюстрирующий основы неизменяемости. Класс FinalClassExample
определяет поля и предоставляет конструктор, который использует глубокое копирование для инициализации объекта. Код в методе main
файла FinalClassExample.java
тестирует неизменяемость объекта.
Создайте новый файл с именем FinalClassExample.java
и скопируйте в него следующий код:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// поля класса FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Функция-геттер для изменяемых объектов
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// Метод-конструктор, выполняющий глубокое копирование
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// Ключевое слово "this" относится к текущему объекту
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
// Тестирование неизменяемого класса
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// вывод значений ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// изменение значений локальных переменных
i=20;
s="modified";
h1.put("3", "third");
// снова выводим значения
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Скомпилируйте и запустите программу:
- javac FinalClassExample.java
- java FinalClassExample
Примечание: При компиляции файла вы можете получить следующее сообщение: Примечание: FinalClassExample.java использует непроверенные или небезопасные операции
, потому что метод-геттер использует непроверенное приведение типа от HashMap<String,String>
к Object
. Вы можете игнорировать предупреждение компилятора для целей этого примера.
Вы получите следующий вывод:
OutputPerforming Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
Вывод показывает, что значения HashMap не изменились, потому что конструктор использует глубокое копирование, а функция-геттер возвращает клон исходного объекта.
Что происходит, когда вы не используете глубокое копирование и клонирование
Вы можете внести изменения в файл FinalClassExample.java
, чтобы показать, что происходит, когда вы используете поверхностное копирование вместо глубокого копирования и возвращаете объект вместо копии. Объект больше не является неизменяемым и может быть изменен. Внесите следующие изменения в примерный файл (или скопируйте и вставьте из примера кода):
- Удалите метод конструктора, предоставляющий глубокое копирование, и добавьте метод конструктора, предоставляющий поверхностное копирование, который выделен в следующем примере.
- В функции getter удалите
return (HashMap<String, String>) testMap.clone();
и добавьтеreturn testMap;
.
Теперь примерный файл должен выглядеть следующим образом:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// поля класса FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Функция Getter для изменяемых объектов
public HashMap<String, String> getTestMap() {
return testMap;
}
// Метод конструктора, выполняющий поверхностное копирование
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
// Тестируем неизменяемый класс
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// выводим значения ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// изменяем значения локальных переменных
i=20;
s="modified";
h1.put("3", "third");
// выводим значения снова
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Скомпилируйте и запустите программу:
- javac FinalClassExample.java
- java FinalClassExample
OutputPerforming Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
На выходе показано, что значения HashMap изменились, поскольку конструктор использует поверхностное копирование, и в функции получения есть прямая ссылка на оригинальный объект.
Заключение
Вы узнали некоторые общие принципы создания неизменяемых классов в Java, включая важность глубокого копирования. Продолжайте обучение с другими учебными пособиями по Java.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java