2. 불변 객체
기본형과 참조형의 공유
자바의 데이터 타입을 가장 크게 보면 기본형(Primitive Type) 과 참조형(Reference Type) 으로 나눌 수 있다.
기본형 : 하나의 값을 여러 변수에서 절대로 공유하지 않는다.
예를 들어,
b = a
라는 기본형 타입 연산이 있다고 하면, 자바는 항상a
의 값을 복사해서 대입한다.결과적으로
a
,b
모두 같은 숫자값을 가지지만,a
가 가진 숫자값과b
가 가진 숫자값은 완전히 다른 값이다.여기서 다르다는 의미는
a
,b
는 아예 다른 메모리 공간에 저장되는 공간이 다르다는 의미로 해석한다.
참조형 : 하나의 객체의 참조값을 통해 여러 변수에서 공유할 수 있다.
공유 참조와 사이드 이펙트
사이드 이펙트(Side Effect) 는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 말한다.
다음의 코드는 사이드 이펙트가 발생할 수 있다.
여러 변수에서 같은 레퍼런스 값을 공유하는 경우 사이드이펙트가 발생할 수 있다.
사이드 이펙트는 본래 부정적인 의미로 사용되는데, 이로 인해 디버깅이 어려워지고 코드의 안정성이 저하될 수 있다.
사이드 이팩트 해결 방안
해결방안은 생각보다 간단하다 a
, b
가 각각 인스턴스를 생성하면 된다.
그러나 여기서 문제가 발생하는데, 하나의 객체를 여러 변수가 공유하지 않도록 강제로 막을 수 있는 방법이 없다는 것이다. (문법적으로 틀린 것이 없다! -> 컴파일 에러 발생 X)
불변 객체 - 도입
지금까지 발생한 문제를 잘 생각해보면 공유하면 안되는 객체를 여러 변수에서 공유했기 때문에 발생한 문제이다. 하지만 앞서 살펴보았듯이 레퍼런스의 공유를 막을 수 있는 방법은 없다.
그런데 사이드 이펙트의 더 근본적인 원인은 객체를 공유하는 것 자체는 문제가 아니다. 객체를 공유한다고 바로 사이드 이펙트가 발생하지는 않는다. 문제의 직접적인 원인은 공유된 객체의 값을 변경한 것에 있다.
진짜 문제는 이후에 b 가 공유 참조하는 인스턴스의 값을 변경하기 때문에 발생한다.
불변 객체 도입
객체의 상태(객체 내부의 값, 필드, 멤버 변수) 가 변하지 않는 객체를 불변 객체(Immutable Object) 라고 한다.
앞서 사용했던 Address
클래스를 불변 상태로 다시 만들어보자.
내부 값이 변경되면 안된다. 따라서
value
의 필드를final
로 선언했다.값을 변경할 수 있는
setValue()
를 제거했다.이 클래스는 생성자를 통해서만 값을 설정할 수 있고, 이후에는 값을 변경하는 것이 불가능하다.
불변 클래스를 만드는 방법은 아주 단순하다. 어떻게든 필드 값을 변경할 수 없게 클래스를 설계하면 된다.
정리
불변이라는 단순한 제약을 사용해서 사이드 이팩트라는 큰 문제를 막을 수 있다.
객체의 공유 참조를 막을 수 없다. 그래서 객체의 값을 변경하는 다른 곳에서 참조하는 변수의 값도 함께 변경되는 사이드 이팩트가 발생한다. 사이드 이팩트가 발생하면 안되는 상황이라면 불변 객체를 만들어서 사용하면 된다.
불변 객체는 값을 변경할수 없다. 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 한다. 이렇게 하면 기존 변수들이 참조하는 값에는 영향을 주지 않는다.
불변 객체 - 값 변경
불변 객체를 사용하지만 그래도 값을 변경해야 하는 메서드가 필요할 때가 있다.
예를 들어서 기존 값에 새로운 값을 더하는 add()
와 같은 메서드가 있다.
이때는 기존 객체의 값을 그대로 두고 대신에 변경된 결과를 새로운 객체에 담아서 반환하면 된다. 결과를 보면 기존 값이 그대로 유지되는 것을 확인할 수 있다.
정리
지금까지 왜 이렇게 불변 객체 이야기를 많이 했을까?
자바에서 가장 많이 사용되는 String
클래스가 바로 불변 객체이기 때문이다. 뿐만 아니라 자바가 기본으로 제공하는 Integer
, LocalDate
등 수 많은 클래스가 불변으로 설계되어 있다. 따라서 불변 객체가필요한 이유와 원리를 제대로 이해해야, 이런 기본 클래스들도 제대로 이해할 수 있다.
모든 클래스를 불변으로 만드는 것은 아니다.
우리가 만드는 대부분의 클래스는 값을 변경할 수 있게 만들어진다. 예를 들어서 회원 클래스의 경우 회원의 여러 속성 을 변경할 수 있어야 한다. 가변 클래스가 더 일반적이고, 불변 클래스는 값을 변경하면 안되는 특별한 경우에 만들어서 사용한다고 생각하면 된다. 때로는 같은 기능을 하는 클래스를 하나는 불변으로 하나는 가변으로 각각 만드는 경우도 있다.
✅ 불변 객체(Immutable Object)를 사용해야 하는 이유
1. 🔒 스레드 안전(Thread-Safe)하게 만들 수 있다.
불변 객체는 상태가 바뀌지 않기 때문에 락 없이도 여러 스레드에서 안전하게 공유 가능하다.
멀티스레드 환경에서도 값이 변하지 않으므로 경합 조건(race condition) 이 발생하지 않는다 .
2. 🔍 상태 추적과 디버깅이 쉬워진다.
객체의 상태가 한 번 정해지면 바뀌지 않으므로, "언제, 어디서 바뀌었는지" 고민할 필요가 없다.
이는 디버깅, 테스트, 로직 추적을 단순하게 만들며 유지 보수성을 높인다.
특히 멀티스레딩 같이 디버깅 하기 힘든 환경에서 디버깅이 쉬워진다는 의미는 크다!
3. 🚫 사이드 이펙트(side effect) 없는 메서드 구성이 가능하다.
불변 객체는 상태 변경이 없으므로, 모든 메서드가 순수 함수처럼 동작한다.
순수 함수란?
동일한 입력이 들어오면 항상 동일한 출력을 반환하고,
사이드 이펙트가 없는 함수를 말한다.
불변 객체는순서 함수를 가능하게 만들고,
순수 함수는 자연스레 불변 객체를 반환한다.
사이드 이펙트가 없기 때문에, 테스트가 쉽고 예측이 가능하다.
4. 🧩 컬렉션(HashMap, HashSet 등)에서 안전하게 사용할 수 있다.
이건 실무에서 자주 발생할 수 있는 문제이다..
해시 기반 컬렉션(
HashMap
,HashSert
) 은 내부적으로hashCode()
,equals(
) 값을 사용하여 데이터를 저장한다.그런데 key 로 들어간 객체가 나중에 내부 상태를 바꾼다면?
컬렉션 내부의 해시값은 갱신되지 않기 때문에, contains(), remove() 같은 메서드가 동작하지 않게 된다..
🔒 불변 객체를 사용하면 이런 문제가 발생하지 않습니다.
컬렉션에서 키로 사용되는 객체는 반드시 불변이거나
hashCode()
와 관련 없는 필드만 수정해야 합니다.
Last updated