가변 객체는 Java에서 Class의 인스턴스가 생성된 이후 내부 상태가 변경 가능한 객체다. 가변 객체는 멀티 스레드 환경에서 사용하려면 별도의 동기화 처리가 필요하며, 대표적인 가변 객체로는 ArrayList
, HashMap
, StringBuilder
, StringBuffer
등이 존재한다. 이외에도 개발자가 커스텀 객체를 생성하여 객체 내부 상태를 변경할 수 있게 만든다면, 이것 또한 가변 객체다.
불변 객체는 가변 객체와 반대로 Java에서 Class의 인스턴스가 생성된 이후 객체 내부 상태를 변경할 수 없게 만든 객체다. 불변 객체는 멀티 스레드 환경에서 안전하게 사용할 수 있다는 신뢰성을 보장하며, 대표적인 불변 객체는 String
이다. 이외에도 개발자가 만든 커스텀 객체도 불변 객체로 만들 수 있다.
thread-safe 하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 된다.
멀티 스레드 환경에서 동기화 문제가 발생하는 이유는 공유 자원에의 동시 쓰기 연산 때문이다. 하지만 공유 자원이 불변 객체라면, 항상 동일한 값만 반환하므로 동기화를 고려하지 않아도 된다. 이는 안정성을 보장할 뿐 아니라 동기화를 하지 않음으로서 성능 상의 이점도 가져다 준다.
실패 원자적인(Failture Atomic) 메소드를 만들 수 있다.
가변 객체를 통해 어떠한 작업을 하는 도중 예외가 발생하면, 해당 객체가 불안정한 상태에 빠질 수 있다. 그리고, 불안정한 상태를 갖는 객체는 변경된 상태로 인해 새로운 오류를 발생시킬 수 있다. 하지만 불변 객체라면 어떤 예외가 발생해도 메소드 호출 전의 상태를 유지할 수 있으므로, 예외가 발생하더라도 변경된 상태로 인한 추가 오류를 예방할 수 있다.
Cache
, Map
, Set
등의 요소로 활용하기에 적합하다.
만약 캐시나 Map
또는 Set
등으로 사용되는 객체가 변경되었다면, 이를 갱신하는 등의 작업이 필요하다. 하지만 객체가 불변이라면, 한 번 데이터가 저장된 이후에 다른 부가 작업을 고려하지 않아도 될 것이고, 이는 캐시나 다른 자료구조를 사용하는데 용이하게 작용한다.
부수 효과(side effect)를 피해 오류 가능성을 최소화할 수 있다.
만약 객체의 수정자(setter)가 구현되어 있고, 여러 메소드에서 객체의 값을 변경한다면, 객체를 예측하기 어려워진다. 그래서 이런 부수 효과가 없는 순수 함수를 만드는 것이 중요한데, 객체가 불변이라면, 변경 가능성이 적어 객체의 생성과 사용이 제한된다. 그러므로, 메소드들은 자연스럽게 순수 함수로 구성되고, 다른 메소드가 호출되어도 객체의 상태가 유지되어 객체를 안전하게 사용할 수 있게 된다. 불변 객체는 오류를 줄여 유지 보수성이 높은 코드를 작성하게 해준다.
다른 사람이 작성한 함수를 예측 가능하며, 안전하게 사용할 수 있다.
불변 객체는 값이 변하지 않음을 보장하므로, 다른 사람의 코드를 변경에 대한 불안 없이 사용할 수 있다.
가비지 컬렉션의 성능을 높일 수 있다.
불변 객체는 read-only 메소드만 제공하며, 객체 내부 상태를 알려주는 메소드를 제공하지 않거나 제공할 경우 방어적 복사 또는 Unmodified
를 통해 제공한다.
또한, 객체의 필드는 모두 final
을 사용하여 처음 할당된 이후 상태가 바뀌지 않도록 해야 한다. 다만, 무조건 final
을 사용한다고 해서 해당 객체를 불변 객체라고 부를 수는 없다.
필드가 모두 primitive type인 경우
public class Car {
private final String name;
private final int position;
public Car(String name, int position) {
this.name = name;
this.position = position;
}
// 필요하다면 getter만 사용. setter는 금지
}
원시 타입은 참조 값이 존재하지 않기 때문에, 값을 그대로 외부로 내보내는 경우에도 내부 객체는 불변이므로, setter가 없고 원시 타입 필드에 대해 final
로 선언했다면 해당 객체는 불변 객체가 된다.