자바에서 불변 클래스를 만드는 방법

소개

이 글은 Java 프로그래밍에서 불변 클래스를 생성하는 방법에 대한 개요를 제공합니다.

객체는 초기화된 후 상태가 변경되지 않으면 불변합니다. 예를 들어, String은 불변 클래스이며, 인스턴스화된 후 String 객체의 값은 절대 변경되지 않습니다. Java에서 String 클래스가 불변인 이유에 대해 자세히 알아보세요.

불변 객체는 업데이트할 수 없으므로 상태가 변경될 때마다 새로운 객체를 생성해야 합니다. 그러나 불변 객체는 다음과 같은 이점이 있습니다:

  • 값이 변경되는 것에 대해 걱정할 필요가 없으므로 불변 클래스는 캐싱 용도로 좋습니다.
  • 불변 클래스는 본질적으로 스레드 안전하므로 멀티 스레드 환경에서 스레드 안전성에 대해 걱정할 필요가 없습니다.

Java에서 멀티 스레딩에 대해 더 알아보고 Java 멀티 스레딩 인터뷰 질문을 찾아보세요.

Java에서 불변 클래스 생성

Java에서 불변 클래스를 생성하려면 다음의 일반 원칙을 따르면 됩니다:

다음 클래스는 불변성의 기본을 보여주는 예시입니다. FinalClassExample 클래스는 필드를 정의하고 객체를 초기화하는 데 깊은 복사를 사용하는 생성자 메서드를 제공합니다. FinalClassExample.java 파일의 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;
	}

	// 가변 객체에 대한 Getter 함수

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

	}

}

프로그램을 컴파일하고 실행합니다:

  1. javac FinalClassExample.java
  2. java FinalClassExample

참고: 파일을 컴파일할 때 다음 메시지를 받을 수 있습니다: Note: FinalClassExample.java uses unchecked or unsafe operations 이는 getter 메서드가 HashMap<String,String>Object로 미확인된 캐스트를 사용하기 때문에 컴파일러 경고가 발생합니다. 이 예제의 목적을 위해 컴파일러 경고를 무시할 수 있습니다.

다음 출력을 얻습니다:

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}

출력 결과는 생성자가 깊은 복사를 사용하고 getter 함수가 원본 객체의 복제본을 반환하기 때문에 HashMap 값이 변경되지 않았음을 보여줍니다.

깊은 복사 및 복제를 사용하지 않을 때 어떤 일이 발생하는지

FinalClassExample.java 파일을 변경하여 얕은 복사 대신 깊은 복사를 사용하고 객체를 반환하는 경우 어떤 일이 발생하는지 보여줄 수 있습니다. 객체는 더 이상 불변이 아니며 변경할 수 있습니다. 예제 파일에 다음 변경 사항을 가하십시오 (또는 코드 예제에서 복사하여 붙여 넣으십시오):

  • 깊은 복사를 제공하는 생성자 메서드를 삭제하고 다음 예제에서 강조 표시된 얕은 복사를 제공하는 생성자 메서드를 추가하십시오.
  • 게터 함수에서 return (HashMap<String, String>) testMap.clone();를 삭제하고 return testMap;을 추가하십시오.

예제 파일은 이제 다음과 같아야합니다:

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

	}

}

프로그램을 컴파일하고 실행하십시오:

  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 값이 변경되었습니다. getter 함수에는 원래 객체에 대한 직접 참조가 있습니다.

결론

자바에서 불변 클래스를 생성할 때 따를 일반 원칙 몇 가지를 배웠습니다. 이에는 깊은 복사의 중요성이 포함됩니다. 더 많은 자바 튜토리얼로 학습을 계속하세요.

Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java