소개
이 글은 Java 프로그래밍에서 불변 클래스를 생성하는 방법에 대한 개요를 제공합니다.
객체는 초기화된 후 상태가 변경되지 않으면 불변합니다. 예를 들어, String
은 불변 클래스이며, 인스턴스화된 후 String
객체의 값은 절대 변경되지 않습니다. Java에서 String
클래스가 불변인 이유에 대해 자세히 알아보세요.
불변 객체는 업데이트할 수 없으므로 상태가 변경될 때마다 새로운 객체를 생성해야 합니다. 그러나 불변 객체는 다음과 같은 이점이 있습니다:
- 값이 변경되는 것에 대해 걱정할 필요가 없으므로 불변 클래스는 캐싱 용도로 좋습니다.
- 불변 클래스는 본질적으로 스레드 안전하므로 멀티 스레드 환경에서 스레드 안전성에 대해 걱정할 필요가 없습니다.
Java에서 멀티 스레딩에 대해 더 알아보고 Java 멀티 스레딩 인터뷰 질문을 찾아보세요.
Java에서 불변 클래스 생성
Java에서 불변 클래스를 생성하려면 다음의 일반 원칙을 따르면 됩니다:
다음 클래스는 불변성의 기본을 보여주는 예시입니다. FinalClassExample
클래스는 필드를 정의하고 객체를 초기화하는 데 깊은 복사를 사용하는 생성자 메서드를 제공합니다. FinalClassExample.java
파일의 main
메서드에 있는 코드는 객체의 불변성을 테스트합니다.
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());
}
}
프로그램을 컴파일하고 실행합니다:
- javac FinalClassExample.java
- java FinalClassExample
참고: 파일을 컴파일할 때 다음 메시지를 받을 수 있습니다: Note: FinalClassExample.java uses unchecked or unsafe operations
이는 getter 메서드가 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}
출력 결과는 생성자가 깊은 복사를 사용하고 getter 함수가 원본 객체의 복제본을 반환하기 때문에 HashMap 값이 변경되지 않았음을 보여줍니다.
깊은 복사 및 복제를 사용하지 않을 때 어떤 일이 발생하는지
FinalClassExample.java
파일을 변경하여 얕은 복사 대신 깊은 복사를 사용하고 객체를 반환하는 경우 어떤 일이 발생하는지 보여줄 수 있습니다. 객체는 더 이상 불변이 아니며 변경할 수 있습니다. 예제 파일에 다음 변경 사항을 가하십시오 (또는 코드 예제에서 복사하여 붙여 넣으십시오):
- 깊은 복사를 제공하는 생성자 메서드를 삭제하고 다음 예제에서 강조 표시된 얕은 복사를 제공하는 생성자 메서드를 추가하십시오.
- 게터 함수에서
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;
}
// 가변 객체에 대한 게터 함수
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 값이 변경되었습니다. getter 함수에는 원래 객체에 대한 직접 참조가 있습니다.
결론
자바에서 불변 클래스를 생성할 때 따를 일반 원칙 몇 가지를 배웠습니다. 이에는 깊은 복사의 중요성이 포함됩니다. 더 많은 자바 튜토리얼로 학습을 계속하세요.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java