Как создать неизменяемый класс в Java
Введение
В этой статье представлен обзор того, как создать неизменяемый класс в программировании на Java.
Объект является неизменяемым, если его состояние не меняется после инициализации. Например, почему класс String
неизменяем в Java.
Поскольку неизменяемый объект нельзя обновить, программам необходимо создавать новый объект для каждого изменения состояния. Однако неизменяемые объекты также имеют следующие преимущества:
- Неизменяемый класс удобен для целей кэширования, поскольку вам не нужно беспокоиться об изменении значения.
- Неизменяемый класс по своей природе потокобезопасен, поэтому вам не нужно беспокоиться о безопасности потоков в многопоточных средах.
Узнайте больше о вопросах для собеседования по Java Multi-Threading.
Создание неизменяемого класса в Java
Чтобы создать неизменяемый класс в Java, вам необходимо следовать этим общим принципам:
- Объявите класс как
final
, чтобы его нельзя было расширить. - Сделайте все поля
личными
, чтобы прямой доступ был запрещен. - Не предоставляйте методы установки для переменных.
- Сделайте все изменяемые поля
final
, чтобы значение поля можно было назначить только один раз. - Инициализируйте все поля с помощью метода конструктора, выполняющего глубокое копирование.
- Выполняйте клонирование объектов в методах получения, чтобы возвращать копию, а не возвращать реальную ссылку на объект.
Следующий класс является примером, иллюстрирующим основы неизменности. Класс FinalClassExample
определяет поля и предоставляет метод конструктора, который использует глубокое копирование для инициализации объекта. Код в методе main
файла FinalClassExample.java
проверяет неизменность объекта.
Создайте новый файл с именем FinalClassExample.java
и скопируйте в него следующий код:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// fields of the FinalClassExample class
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 function for mutable objects
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// Constructor method performing deep copy
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// "this" keyword refers to the current object
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;
}
// Test the immutable class
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);
// print the ce values
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// change the local variable values
i=20;
s="modified";
h1.put("3", "third");
// print the values again
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
в Объект
. Вы можете игнорировать предупреждение компилятора для целей этого примера.
Вы получите следующий вывод:
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
, чтобы показать, что происходит, когда вы используете поверхностное копирование вместо глубокого копирования и возвращаете объект вместо копии. Объект больше не является неизменным и может быть изменен. Внесите следующие изменения в файл примера (или скопируйте и вставьте из примера кода):
- Удалите метод конструктора, обеспечивающий глубокое копирование, и добавьте метод конструктора, обеспечивающий поверхностное копирование, выделенный в следующем примере.
- В функции получения удалите
return (HashMap
и добавьте) testMap.clone(); return testMap;
.
Теперь файл примера должен выглядеть так:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// fields of the FinalClassExample class
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 function for mutable objects
public HashMap<String, String> getTestMap() {
return testMap;
}
//Constructor method performing shallow copy
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;
}
// Test the immutable class
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);
// print the ce values
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// change the local variable values
i=20;
s="modified";
h1.put("3", "third");
// print the values again
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.