Поиск по сайту:

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


Введение

В этой статье представлен обзор того, как создать неизменяемый класс в программировании на Java.

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

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

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

Узнайте больше о вопросах для собеседования по Java Multi-Threading.

Создание неизменяемого класса в Java

Чтобы создать неизменяемый класс в Java, вам необходимо следовать этим общим принципам:

  1. Объявите класс как final, чтобы его нельзя было расширить.
  2. Сделайте все поля личными, чтобы прямой доступ был запрещен.
  3. Не предоставляйте методы установки для переменных.
  4. Сделайте все изменяемые поля final, чтобы значение поля можно было назначить только один раз.
  5. Инициализируйте все поля с помощью метода конструктора, выполняющего глубокое копирование.
  6. Выполняйте клонирование объектов в методах получения, чтобы возвращать копию, а не возвращать реальную ссылку на объект.

Следующий класс является примером, иллюстрирующим основы неизменности. Класс 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());

	}

}

Скомпилируйте и запустите программу:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Примечание. При компиляции файла может появиться следующее сообщение: Примечание. FinalClassExample.java использует непроверенные или небезопасные операции, так как метод получения использует непроверенное приведение из HashMap в Объект. Вы можете игнорировать предупреждение компилятора для целей этого примера.

Вы получите следующий вывод:

Output
Performing 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());

	}

}

Скомпилируйте и запустите программу:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Вы получите следующий вывод:

Output
Performing 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.