불변 객체란 객체 생성 이우 내부의 상태가 변하지 않는 객체이다. 불변 객체는 read-only 메소드만을 제공하며, 객체의 내부 상태를 제공하는 메소드를 제공하지 않거나 제공하는 경우 방어적 복사를 통해 제공한다.
ex) Java의 String이 불변 객체이다.
String name = "Old";
name = "New";
name.toCharArray()[2] = 'o';
Java의 String은 불변 클래스이기 때문에 위와 같이 name이라는 변수에 "New"를 할당하는 것은 내부의 char를 변경하는 것이 아니라 새로운 값을 지니는 객체를 생성하는 것이다. 또한 String 내부의 char형 배열을 얻어 수정하여도 반영이 되지 않는다.
Java에서는 배열이나 객체 등의 참조를 전달한다. 그렇기 때문에 참조를 통해 값을 수정하면 내부의 상태가 변하기 때문에 내부를 복사하여 전달하고 있는데, 이를 방어적 복사라고 한다.
Thread-Safe하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 된다.
멀티 쓰레드 환경에서 동기화 문제가 발생하는 이유는 공유 자원에 동시에 쓰기 때문이다. 하지만 만약 공유 자원이 불변의 자원이라면 더이상 동기화를 고려하지 않아도 될 것이다. 왜냐하면 항상 동일한 값을 반환할 것이기 때문이다. 이는 안전성을 보장할 뿐만 아니라 동기화를 하지 않음으로써 성능상의 이점도 있다.
동기화? 시스템의 한정적인 자원에 여러 쓰레드가 동시에 접근해서 사용하려고 하면 문제가 발생할 수 있다. 이를 해결하기 위해 쓰레드 들에게 하나의 자원에 대한 처리 권한을 주거나 순서를 조정해주는 기법이다.
실패 원자적인 메소드를 만들 수 있다.
가변 객체를 통해 어떠한 작업을 하는 도중 예외가 발생하면 해당 객체가 불안정한 상태에 빠질 수 있다. 그리고 불안정한 상태를 갖는 객체는 또 다른 에러를 유발할 수 있다. 하지만 불변 객체라면 어떠한 예외가 발생하여도 메소드 호출 전의 상태를 유지할 수 있을 것이다. 그리고 예외가 발생하더라도 오류가 발생하지 않은 것처럼 다음 로직을 처리할 수 있다.
Cache나 Map 또는 Set 등의 요소로 활용하기에 더욱 적합하다.
만약 캐시나 Map 또는 Set 등으로 사용되는 객체가 변경되었다면 이를 갱신하는 등의 작업을 추가로 해주어야 할 것이다. 하지만 객체가 불변이라면 한번 데이터가 저장된 이후에 다른 부가 작업들을 고려하지 않아도 될 것이고, 이는 캐시나 다른 자료 구조를 사용하는데 용이하게 작용될 것이다.
부수 효과를 피해 오류가능성을 최소화할 수 있다.
객체가 불변이라면 기본적으로 값의 수정이 불가능하기 때문에 변경 가능성이 적으며, 객체의 생성과 사용이 상당히 제한된다. 그렇기 때문에 메소드들은 자연스럽게 순수 함수들로 구성될 것이고, 다른 메소드가 호출되어도 객체의 상태가 유지되기 때문에 안전하게 객체를 다시 사용할 수 있다.
부수 효과란? 변수의 값이 변경되거나, 필드 값이 설정되는 등의 변화가 발생하는 효과를 의미한다. 만약 객체의 수정자(Setter)가 구현되어 있고, 여러 메소드에서 객체의 값이 변경된다면 객체를 예측하기 어려워질 것이다. 객체의 바뀐 상태를 파악하기 위해서는 메소드들을 살펴보아야 할 것이며 이러한 부분은 유지보수성을 상당히 떨어트린다. 그래서 이러한 부수효과가 없는 순수함수를 만드는것이 상당히 중요하다.
다른 사람이 작성한 함수를 예측가능하며 안전하게 사용할 수 있다.
일반적으로 개발을 하면 다른 사람들과 협업을 하면서 진행하게 된다. 불변성은 협업을 하는 과정에서도 도움을 주는데, 불변성이 보장된 함수라면 다른 사람이 개발한 함수를 위험없이 이용할 수 있다. 마찬가지로 다른 사람도 내가 작성한 메소드를 호출하여도, 값이 변하지 않음을 보장받을 수 있다. 그렇기에 우리는 다른사람의 코드를 변경에 대한 불안없이 이용할 수 있다. 또한 불필요한 시간을 절약할 수도 있다.
GC 성능을 높일 수 있다.
불변성을 활용하는 것은 많은 이점을 가져다주는데, 그 중에서 많은 사람들이 놓치는 것이 바로 GC의 성능을 높여준다는 것이다. 불변의 객체는 한번 생성된 이후에 수정이 불가능한 객체로, Java에서는 final 키워드를 사용하여 불변의 객체를 생성할 수 있다. 이렇게 객체를 생성하기 위해서는 객체를 가지는 컨테이너도 존재한다는 것인데, 당연히 불변의 객체가 먼저 생성되어야 컨테이너가 이 객체를 참조할 수 있을 것이다.
즉, 컨테이너는 컨테이너가 참조하는 가장 젊은 객체들보다 더 젊다는 것이다. 이러한 점은 GC가 수행될 때, 가비지 컬렉터가 컨테이너 하위의 불변 객체들은 Skip할 수 있도록 도와준다. 왜냐하면 해당 컨테이너가 살아있다는 것은 하위의 불변 객체들 역시 처음에 할당된 그 상태로 참조되고 있다는 것을 의미한다.