들어가며
앞선 이펙티브 자바 포스팅 중에 간간히 불변객체에 대한 내용이 언급되었다. String 클래스가 대표적인 불변객체다. '불변 객체가 이번 아이템과 어떤 연관관계가 있길래 서두부터 언급을 하지?' 생각하실까봐 아이템 제목을 살짝 변경해보고자 한다.
"될 수 있으면 모든 객체를 불변객체로 생성하라"
이번 아이템은 변경가능성을 최소화한 불변객체에 대한 내용이기 때문에 이렇게 변경해보았다.
불변객체는 무엇인가?
영어로는 Immutable Object라고 하며, 한자 뜻을 풀어보자면 '변하지 않는 객체', '변경 가능성이 없는 객체' 따위로 설명할 수 있다. 위키 백과에서는 '생성 후 그 상태를 바꿀 수 없는 객체'라고 정의하고 있다. 1
어떻게 불변객체를 만들 수 있나?
책에서는 다섯 가지 규칙을 제시한다. 아래는 그 다섯 가지 규칙이다.
- 변경자(흔히 Setter라고 부르는 코드)를 만들지 않는다.
- 클래스의 확장을 막는다.
- 모든 필드를 final로 선언한다.
- 모든 필드를 private로 선언한다.
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
규칙이 너무 많다고 생각이 든다면 한 가지만 기억하면 된다.
"객체의 필드값을 어떤 형태로든 변경할 수 없게 만들자"
간단한 예제 코드를 보자.
public class Member {
private final Long id;
private final String name;
private final Long age;
private Member(Long id, String name, Long age) {
this.id = id;
this.name = name;
this.age = age;
}
public static Member createMember(Long id, String name, Long age) {
return new Member(id, name, age);
}
}
Member 클래스는 불변객체다. 다섯가지 규칙을 하나씩 보자.
- 변경자(흔히 Setter라고 부르는 코드)를 만들지 않는다.
=> 필드값을 변경할 수 있는 변경자가 없다. 오로지 createMember() 메서드를 통해서만 객체를 생성하기 때문에 생성시점에만 필드값을 설정할 수 있다. - 클래스의 확장을 막는다.
=> 상속할 때는 부모의 기본 생성자(default constructor)가 있어야 한다. Member 클래스는 인자를 갖는 생성자가 있어 JVM이 자동으로 기본 생성자를 생성할 수 없다. 따라서 서브클래스에서 상속할 수 없다. - 모든 필드를 final로 선언한다.
=> ID, name, age 모두 final로 선언되어있다. 이 필드는 변경자가 존재해도 변경할 수 없다. - 모든 필드를 private로 선언한다.
=> ID, name, age 모두 private로 선언되어있다. 객체 외부에서는 이 필드값을 호출할 수 없다. - 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
=> 가변 컴포넌트가 존재하지 않는다. 존재하더라도 객체의 값이 변경되지 않도록 해야한다.
모든 규칙을 만족하니 Member 클래스는 불변객체라고 볼 수 있다.
장점이 무엇이길래 불변객체로 개발해야하나?
도대체 어떤 장점이 있길래 이런 규칙까지 지켜가면서 불변객체를 생성해야할까? 하나씩 살펴보면서 불변객체와 친해져보자.
- Thread-safety 한 객체를 생성할 수 있다.
변하지 않는다는 속성을 통해 멀티스레드 환경에서도 데이터를 안전하게 전달하고 전달 받을 수 있다. - 동일한 객체를 캐싱하여 성능향상 및 자원을 아낄 수 있다.
반대로 객체가 변경될 때 마다 새로운 객체를 만들어야하는 것이 단점이 될 수도 있다. - 프로세스가 실패하는 경우에도 객체의 값이 실패 전이나, 실패 후나 동일하다.
조금 있어보이는 말로는 '실패 원자성이 보장된다'라고 표현한다. 생각해보면 변경 자체가 불가능한데 프로세스가 있던, 없던 이 객체는 변하는 일이 없다.
장점을 살펴보니 데이터가 변경되지 않는 것 만으로 꽤나 골치아픈 문제들을 해결해준다. 만약 변경가능한 객체를 사용하면서 Thread-safety를 유지하고, 실패 원자성을 보장하는 코드를 만드려면 큰 노력이 필요할 것이다.
단점은 간단하다. 변경될 때 마다 새로운 객체를 생성해야하므로 불필요한 객체생성이 잦을 수 있다. 이를 극복하기 위해 캐싱으로 객체 재활용을 최대화 해야한다. 책에서는 자주 사용될 Multistep operation을 예측하여 불변객체 내부에 구현해두는 방법도 제시한다.
마치며
변경 가능성을 최소화 할 수 있는 방법으로 불변객체를 학습해보았다. 간단히 학습한 내용을 정리해보자.
불변객체는 변하지 않는 객체다. 책에서 제시하는 다섯 가지 원칙을 지킨다면 불변객체를 생성해 볼 수 있다.
장점으로는 멀티스레드 환경에서도 변경가능성이 없어 자유롭게 주고 받을 수 있고, 실패 원자성도 보장 받을 수 있다. 불필요한 객체가 많이 생성될 수 있다는 단점도 있지만 캐싱과 자주 사용될 연산을 불변객체 내부에 생성해두는 방법으로 극복할 수 있다.
비록 과거에 개발된 코드는 불변성의 장점을 누리지 못한 경우가 많다. 하지만 점점 불변성을 갖는 코드가 많아지고 있으며, 오늘날에는 객체를 설계할 때 불변객체를 전제한다. 독자분들도 앞으로의 개발에 적극적으로 불변객체를 도입해보길 바란다.
![]() |
|
'자바' 카테고리의 다른 글
(이펙티브 자바) 아이템 18. 상속보다는 컴포지션을 사용하라 (0) | 2020.08.18 |
---|---|
(이펙티브 자바) 아이템 16. public 클래스에서는 public 필드가 아닌 다른 접근자 메서드를 사용하라 (0) | 2020.08.18 |
(이펙티브 자바) 아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2020.08.18 |
(이펙티브 자바) 아이템 14. Comparable을 구현할지 고려하라. (0) | 2020.08.17 |
(이펙티브 자바) 아이템 15. 클래스와 멤버의 접근 권한을 최소화하라 (1) | 2020.08.10 |