Java에서는 불변 객체(Immutable Object)를 활용하면 객체의 상태를 안전하게 유지할 수 있다.
특히, 멀티스레드 환경에서 동기화 문제를 피하고, 안정적인 코드 작성을 가능하게 한다.
이번 포스팅에서는 불변 객체가 무엇인지, 어떻게 만드는지, 그리고 불변 객체를 사용해야 하는 이유를 알아보자.
불변 객체(Immutable Object)란 한 번 생성되면 상태를 변경할 수 없는 객체 를 의미한다.
즉, 객체 내부의 필드 값을 변경할 수 없으며, 생성 시 설정된 값이 변하지 않는다.
String
Wrapper Class
(Integer
, Double
, Boolean
등)java.time
패키지의 날짜 클래스 (LocalDate
, LocalTime
, LocalDateTime
등)public class ImmutableStringExample {
public static void main(String[] args) {
String str = "Hello";
str = str.concat(" World"); // 새로운 객체가 생성됨
System.out.println(str); // "Hello World"
}
}
위 코드에서 "Hello"
문자열은 변경되지 않고, concat
메서드를 호출하면 새로운 문자열 객체 가 생성된다. 즉, 기존 str
객체의 값이 바뀌는 것이 아니라, 새로운 참조값이 str
에 저장되는 것이다.
Java에서 불변 객체를 만들려면 다음 규칙을 따라야 한다.
private final
로 선언한다.public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
name
과 age
필드는 private final
로 선언되어 한 번 설정되면 변경할 수 없다.불변 객체와 가변 객체의 차이를 비교해 보자.
class MutablePerson {
private String name;
public MutablePerson(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name; // 상태 변경 가능
}
public String getName() {
return name;
}
}
위 MutablePerson
클래스는 setName()
메서드를 통해 객체의 상태를 변경할 수 있다.
public final class ImmutablePerson {
private final String name;
public ImmutablePerson(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
ImmutablePerson
클래스는 한 번 설정된 name
값을 변경할 방법이 없기 때문에, 객체의 상태가 유지된다.
만약 클래스 내부에 참조형 데이터(예: List
, Date
등) 가 있다면, 단순히 final
을 사용해도 불변성을 보장할 수 없다.
import java.util.ArrayList;
import java.util.List;
public final class WrongImmutableClass {
private final List<String> items; // final이지만 불변 객체가 아님
public WrongImmutableClass(List<String> items) {
this.items = items; // 외부 리스트가 변경되면 내부 상태도 변경됨
}
public List<String> getItems() {
return items; // 외부에서 변경 가능
}
}
위 코드에서 getItems()
를 호출하면 원본 리스트의 참조값이 그대로 반환되므로, 외부에서 리스트를 변경할 수 있다.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class ImmutableClass {
private final List<String> items;
public ImmutableClass(List<String> items) {
this.items = new ArrayList<>(items); // 새로운 리스트로 복사
}
public List<String> getItems() {
return Collections.unmodifiableList(items); // 변경 불가능한 리스트 반환
}
}
getItems()
에서 Collections.unmodifiableList()
를 사용하여 읽기 전용 리스트를 반환 한다.이제 ImmutableClass
는 완전히 불변성을 유지할 수 있다.
불변 객체는 상태가 변경되지 않기 때문에 동기화(Synchronization) 없이도 안전하게 사용할 수 있다.
이 때문에 String
, Integer
, LocalDate
같은 클래스들은 멀티스레드 환경에서도 문제없이 사용된다.
객체의 상태가 변하지 않으므로, 사이드 이펙트(Side Effect) 없이 안전한 코드 작성을 할 수 있다.
즉, 한 번 생성된 객체는 안심하고 사용할 수 있다.
불변 객체는 equals()
와 hashCode()
값이 변하지 않으므로, HashMap
이나 HashSet
의 키(Key)로 사용하기 적합하다.
Java에서 불변 객체(Immutable Object)를 사용하면 객체의 상태를 안전하게 유지할 수 있으며, 멀티스레드 환경에서도 동기화 없이 안전하게 활용할 수 있다. 또한 불변 객체를 만들 때 방어적 복사(Defensive Copy)를 활용하면 내부 참조 데이터까지 안전하게 보호할 수 있다.
따라서 불변 객체를 잘 활용하면 코드의 안정성이 높아지고, 유지보수성이 개선된다.