Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

아이템 88. readObject 메서드는 방어적으로 작성하라 #90

Open
HanaHww2 opened this issue Apr 25, 2023 · 1 comment
Open
Assignees

Comments

@HanaHww2
Copy link
Contributor

No description provided.

@Tldkt
Copy link
Contributor

Tldkt commented May 2, 2023

readObject에서 주의사항

1. 방어적 복사를 수행하는 불변 클래스 개선하기

readObject()

  • 매개변수로 바이트스트림을 받는 생성자
  • 객체 역직렬화 시, 객체 참조를 갖는 필드 모두 방어적 복사 필요
  • 방어적 복사를 유효성 검사보다도 먼저 수행하자

유효성 검사를 먼저 하는 경우에도 어차피 객체 불변성을 보장하기 위해서는 방어적 복사를 해야 하기 때문

방어적 복사(Defensive Copy)?

  • 객체를 복사할 때 해당 객체가 가변 객체인 경우, 객체의 상태를 수정하는 것이 의도치 않게 다른 객체의 상태를 변경할 수 있는 상황을 방지하기 위한 복사 방식

Serializable 만 구현하면 불변식을 보장할 수 있을까?

→ 아님! readObject 메서드도 생성자만큼이나 주의해야 함

2. 예시와 함께 보기

public final class Period {
    private final Date start;
    private final Date end;

    /**
     * @param  start 시작 시각
     * @param  end 종료 시각; 시작 시각보다 뒤여야 한다.
     * @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다.
     * @throws NullPointerException start나 end가 null이면 발생한다.
     */
    public Period(Date start, Date end) {
        this.start = new Date(start.getTime()); // 가변인 Date 클래스의 위험을 막기 위해 새로운 객체로 방어적 복사
        this.end = new Date(end.getTime());

        if (this.start.compareTo(this.end) > 0) {
            throw new IllegalArgumentException(start + " after " + end);
        }
    }

    public Date start() { return new Date(start.getTime()); }
    public Date end() { return new Date(end.getTime()); }
    public String toString() { return start + " - " + end; }
    // ... 나머지 코드는 생략
}

Period 클래스의 생성자에서

  • start와 end 필드에 대해 방어적 복사를 수행

  • start와 end 필드를 반환하는 메서드에서도 방어적 복사를 수행

    → start와 end 필드의 참조값이 노출되는 것을 방지하여 불변 객체처럼 동작하도록 하고 있음

하지만, Period 클래스는 여전히 불변식을 보장하지 못함

  • start와 end 필드가 가변인 Date 클래스의 객체를 참조하고 있기 때문
  • Period 클래스의 사용자가 start와 end 필드가 참조하는 Date 객체를 수정할 수 있다면, Period 객체의 불변성이 깨질 수 있음
Period period = new Period(new Date(), new Date());
Date end = period.end();
end.setYear(100);

Period 객체를 생성한 후, end 필드가 참조하는 Date 객체를 얻어와서 해당 객체의 연도를 100년으로 수정하게 됨

→ 따라서 Date 클래스 대신 불변한 LocalDateTime 클래스나 Instant 클래스 등을 사용해야 하며, start와 end 필드가 참조하는 객체를 완전히 복사하는 방식으로 방어적 복사해야 함

3. 정리하면

  • readObject 메서드를 작성할 때는 언제나 public 생성자를 작성하는 자세로 임해야 한다.
  • readObject 메서드는 어떤 바이트 스트림이 넘어오더라도 유효한 인스턴스를 만들어내야 한다.
    • 이 바이트 스트림이 항상 진짜 직렬화된 인스턴스라고 믿으면 안 된다.
  • 안전한 readObject 메서드를 작성하는 지침
    • private 이여야 하는 객체 참조 필드는 각 필드가 가리키는 객체를 방어적으로 복사하라.
    • 모든 불변식을 검사하고, 어긋난다면 InvalidObjectException을 던져라.
    • 역직렬화 후 객체 그래프 전체의 유효성을 검사해야 한다면 ObjectInputValidation를 사용하라.
    • 재정의(Overriding) 가능한 메서드는 호출하지 말자.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants