본문 바로가기

언어 기초/JAVA

불변객체 [JAVA | 학습을 위한 자료 | 김영한 자바 중급 1편]

하나의 객체를 여러 변수가 공유하지 않도록 강제로 막을 수 있는 방법이 없다.

객체의 상태(객체 내부의 값, 필드, 멤버 변수)가 변하지 않는 객체를 불변 객체(Immutable Object)라 한다.

1. 필드를 final로 선언

객체의 상태를 변경하지 않기 위해 필드들을 final로 선언한다. final 키워드는 필드가 한번 초기화된 후에는 다시 변경될 수 없음을 보장한다.

public class ImmutableClass {
    private final String name;
    private final int age;
}

2. 생성자를 통해 모든 필드를 초기화

객체가 생성될 때 모든 필드가 초기화되도록 생성자를 사용한다. 이를 통해 객체가 생성될 때 필요한 값들이 반드시 설정되도록 강제할 수 있다.

public ImmutableClass(String name, int age) {
    this.name = name;
    this.age = age;
}

3. Getter만 제공하고 Setter는 제공하지 않음

객체의 필드를 외부에서 변경할 수 없도록 필드에 접근하는 메서드는 읽기 전용(Getter)만 제공하고, 필드를 수정하는 메서드(Setter)는 제공하지 않는다.

public String getName() {
    return name;
}

public int getAge() {
    return age;
}

4. 객체 필드가 가변 객체일 경우 방어적 복사

클래스 내부에서 가변 객체(Mutable Object)를 필드로 가지고 있을 경우, 그 객체를 반환할 때는 원본 객체를 직접 노출하지 않도록 방어적 복사(Defensive Copy)를 한다.

예를 들어, Date 객체는 가변 객체이므로 반환할 때 원본 객체가 아닌 복사본을 반환해야 한다.

public class ImmutableClass {
    private final Date birthDate;

    public ImmutableClass(Date birthDate) {
        this.birthDate = new Date(birthDate.getTime()); // 방어적 복사
    }

    public Date getBirthDate() {
        return new Date(birthDate.getTime()); // 방어적 복사
    }
}

5. 클래스를 final로 선언

객체를 불변으로 유지하기 위해서는 클래스를 상속할 수 없게 만들어야 한다. 이를 위해 클래스 자체를 final로 선언하면 하위 클래스에서 필드를 변경할 수 없게 된다.

public final class ImmutableClass {
    // 클래스 내용
}

자바에서 가장 많이 사용되는 String 클래스가 바로 불변 객체이기 때문이다. 뿐만 아니라 자바가 기본으로 제공하는 Integer, LocalDate 등 수 많은 클래스가 불변으로 설계되어 있다

문제와 풀이

  • MyDate클래스는 불변이 아니어서 공유 참조시 사이드 이펙트가 발생한다. 이를 불변 클래스로 만들어라.
  • 새로운 불변 클래스는 ImmutableMyDate 로 이름 지으면 된다.
  • 새로운 실행 클래스는 ImmutableMyDateMain 으로 이름 지으면 된다.
public class MyDate {
     private int year;
     private int month;
     private int day;
     
     public MyDate(int year, int month, int day) {
         this.year = year;
         this.month = month;
         this.day = day;
		}
     public void setYear(int year) {
         this.year = year;
		}
     public void setMonth(int month) {
         this.month = month;
     }
     public void setDay(int day) {
     this.day = day;
     }
     
     @Override
     public String toString() {
         return year + "-" + month + "-" + day;
     }
}
public class MyDateMain {
     
     public static void main(String[] args) {
         MyDate date1 = new MyDate(2024,1,1);
         MyDate date2 = date1;
         System.out.println("date1 = " + date1);
         System.out.println("date2 = " + date2);
         
         System.out.println("2025 -> date1");
         date1.setYear(2025);
         System.out.println("date1 = " + date1);
         System.out.println("date2 = " + date2);
		}
}
실행결과
date1 = 2024-1-1
date2 = 2024-1-1
2025 -> date1
date1 = 2025-1-1
date2 = 2025-1-1