Java/기본

[Java] Java에서 final로 진짜_최종_최종_최종 불변 객체 만들기

댕주 2024. 10. 31. 21:23

이전 글에서 final을 앞에 달아주기만 하면 무적의 불변 객체가 되 .... .는줄 알았으나 ....

불변 객체가 기본적으로 안전성을 높여주지만, 외부에서 참조하는 가변 객체를 포함하거나, 불변 객체의 일부 상태가 방어적 복사 없이 외부로 노출되는 경우 안전하지 않을 수 있다는 것. !!

 

그렇다면 불변 객체의 안전을 위협하는 상황에 대해서 알아보자

1. 가변 객체를 포함할 때

- 불변 객체 내부에 가변 객체가 포함되면, 해당 가변 객체의 상태가 외부에서 변경될 가능성이 생긴다. 예를 들어 Date 객체는 가변이므로, final 필드에 포함되더라도 객체 내부 상태가 바뀔 수 있다.

public final class Period {
    private final Date startDate;
    private final Date endDate;
    
    public Period(Date startDate, Date endDate) {
        this.startDate = new Date(startDate.getTime()); // 방어적 복사
        this.endDate = new Date(endDate.getTime());
    ]
    
    public Date getStartDate() {
        return new Date(startDate.getTime()); // 방어적 복사
    }
    
    public Date getEndDate() {
        return new Date(endDate.getTime());
    }
}

 

만약 Date 객체를 방어적 복사 없이 그대로 할당하면, 외부에서 startDate 와 endDate 를 참조해서 상태를 변경할 수 있다. 이를 막으려면 생성자와 접근자에서 방어적 복사를 수행하는 것이 중요!!(별 다섯개)

 

2. 컬렉션과 같은 가변 데이터 구조를 포함할 때

- 만약 불변 객체가 List 나 Map 같은 컬렉션을 포함한다면, 이 컬렉션 역시 가변이기 때문에 외부에서 해당 컬렉션을 수정할 위험이 있다. 불변 객체는 불변 컬렉션을 사용하거나 방어적 복사를 통해 안전하게 컬렉션을 관리해야한다

- Collections.unmodifiableList() 와 같은 메서드를 사용해 변경 불가능한 뷰를 제공하거나, 전체 컬렉션을 복사하여 제공하는 방법이 있다.

 

3. 클래스 외부로 가변 객체를 반환할 때

- 불변 객체의 일부 데이터가 외부에 노출되어 외부에서 변경 가능한 경우, 불변성이 깨질 수 있다. 내부 필드를 그대로 반환하는 대신 새로운 객체를 생성해 반환하거나 불변 객체로 반환하는 것이 안전하다.

public final class Person {
    private final String name;
    private final List<String> friends; // 친구 목록 (가변 객체)
    
    public Person(String name, List<String> friends) {
        this.name = name;
        this.friends = friends; // 원본 리스트를 그대로 할당
    }
    
    public String getName() {
        return name;
    }
    
    public List<String> getFriends() {
        return friends; // 가변 객체를 직접 반환
    }
}

// 불변 객체의 불변성이 깨지는 예시
public class Main {
    public static void main(String[] args) {
        List<String> friends = new ArrayList<>();
        friends.add("Alice");
        Person person = new Person("John", friends);
        
        person.getFriends(); // [Alice]
        
        person.getFriends().add("Bob");
        
        person.getFriends(); // [Alice, Bob]
    }
}
// 불변 객체의 불변성을 유지하는 방법
public final class Person {
    private final String name;
    private final List<String> friends;
    
    public Person(String name, List<String> friends) {
        this.name = name;
        this.friends = new ArrayList<>(friends); // 방어적 복사
    }
    
    public String getName() {
        return name;
    }
    
    public List<String> getFriends() {
        return Collections.unmodifiableList(friends); // 불변 리스트 반환
    }
}

 

 

4. 직렬화할 때

- 불변 객체가 직렬화된 경우, 역직렬화 시에 공격자가 생성자를 우회해 내부 상태를 변경할 수 있다. 이를 막기 위해 직렬화된 객체가 올바른 형태로 재생성되도록 readResolve() 메서드를 사용해 불변성을 유지해야 한다.

'Java > 기본' 카테고리의 다른 글

[Java] CompletableFuture  (0) 2024.11.08
[Java] Future  (0) 2024.11.08
[Java] Java에서 final로 불변 객체 만들기  (0) 2024.10.30
[Java] 스트림(Stream)  (0) 2024.10.27
[Java] 표준 함수형 인터페이스  (0) 2024.10.27