개발은 아름다워

[ Java ] 불변객체는 왜 필요한걸까? ver2 본문

자바

[ Java ] 불변객체는 왜 필요한걸까? ver2

do_it_zero 2024. 10. 24. 16:58

자바에서 제공하는 굉장히 많은 클래스들이 불변 객체로 설계가 되어 있다.

불변 객체는 프로그래밍에서도 중요한 개념이다.

기반 개념으로 기본형과 참조형의 공유에 대해서 알아야한다.

기본형과 참조형의 공유

자바의 데이터 타입을 가장 크게 보면 기본형(Primitive Type)과 참조형(Reference Type)으로 나눌 수 있다.

  • 기본형 : 하나의 값을 여러 변수에서 절대 공유하지 않음
  • 참조형 : 하나의 객체 참조값을 통해 여러 변수에서 공유

자바는 항상 값을 복사해서 대입한다.
기본형도 값을 복사한다. 하지만 값을 공유하는 것은 아니다.
int a = 10;
int b = a;
값을 복사기 때문에 b 의 값이 10이 된다

b=20;이라면
b의 값만 20이 되며 a의 값은 10 그대로이다.

왜냐하면 기본형은 값을 공유하지 않기 때문이다.

 

기본형 변수는 절대로 같은 값을 공유하지 않는다. 같은 모양의 숫자를 가질 수 있지만, 같은 값을 공유하는 것이 아니다.

 

참조형 변수는 참조값을 통해 같은 객체를 공유할 수 있다.

User userA = new User("A"); // 참조값 x001
User userB = userA; // userB = x001
이 경우 userA와 userB의 name은 둘 다 A가 된다.

같은 객체를 바라보고 있기 때문이다.

userB.setName("B"); // x001.setName("B");
인 경우 어떻게 될까??
userB의 name은 당연히 B가 되고 userA의 name 또한 B가 된다.

왜냐하면 참조값이 변한 것이 아니기 때문이다.

즉, 같은 객체를 바라보고 있기 때문이다.

다시 반복하자면 참조형 변수는 하나의 객체 참조값을 통해 여러 변수에서 공유하는 것이다.

 

만약 위의 의도가 userB의 name의 값만 B로 바꾸는 것이였다면?

의도와 같지 않게 된다. 이렇게 의도와 다른 추가적인 부수 효과를 일으키는 것을 사이드 이펙트라 한다.

공유 참조와 사이드 이펙트

사이드 이펙트는 프로그램의 특정 부분에서 발생한 병경이 의도치 않게 다른 부분에 영향을 미치는 경우에 발생한다.

이로 인해 디버깅이 어려워지고 코드의 안정성이 저하될 수 있다.

자바 문법상의 오류가 아니라 개발자의 의도와는 다른 것이다.
위의 예제에서 userB의 name만 바꾸려면 userB가 userA와 다른 참조값을 갖도록 만들면 된다.

즉, 다른 객체를 생성하면 되는 것이다.

 

User userA = new User("A"); // 참조값 x001
User userB = new User("A"); // 참조값 x002

userB.setName("B") // x002의 name만 B로 바꿈!

 

이처럼 단순하게 서로 다른 객체를 참조해서, 같은 객체를 공유하지 않으면 문제가 해결된다.

여러 변수가 객체를 공유하지 않으면 위와 같은 문제가 발생하지 않는 것이다. 

그런데 문제는 여러 변수가 하나의 객체를 공유하지 않도록 막을 수 있는 방법이 없다는 것이다.

기본형은 항상 값을 복사해서 대입하기 때문에 값이 절대로 공유되지 않는다.

하지만 참조형의 경우 참조값을 복사해서 대입하기 때문에 여러 변수에서 얼마든지 같은 객체를 공유할 수 있다.
객체를 공유하는게 맞을 때도 있지만, 때로는 공유하는 것이 문제가 될 수 있다.

물론 위와 같이 간단한 경우 바로 파악할 수 있지만, 코드가 복잡해질 경우 의도를 파악하기 어려울 수 있다.

 

불변 객체 - 도입

사이드 이펙트의 근본적인 원인을 고려해보면, 객체를 공유하는 것 자체는 문제가 아니다.

객체를 공유한다고 해서 사이드 이펙트가 발생하지 않는다.


문제의 직접적인 원인은 공유된 객체의 값을 변경한 것에 있다.

 

참조형인 객체는 처음부터 공유될 수 있도록 설계되었다. 이것이 문제가 아니다.
문제의 직접적인 원인은 공유된 객체의 값을 (의도와 다르게) 어디선가 변경했기 때문이다 .

 

어디서든 필드를 변경하지 못하게 만드는 객체를  만드는 것, 그것이 불변 객체이다.

불변 객체의 핵심은 생성 후 객체의 필드 값이 바뀌지 않는다는 것이다.

생성 후 절대 필드 값을 바꾸지 못하게 하면 불변 객체가 되는 것이다.

 

final을 넣어주면 의도를 더욱 명확하게 알 수 있다.

  • 가변 객체 : 가변은 이름 그대로 처음 만든 이후 상태가 변할 수 있다는 뜻이다.
  • 불변 객체 : 이름 그대로 처음 만든 이후 상태가 변하지 않는다는 것이다.

불변 객체 자체를 만드는 대신 가변 객체를 새로 만들어서 새로운 필드값을 가진 객체 만들면 된다.

하지만 가변 객체인 경우 명확성이 떨어진다.

왜냐하면 가변 객체의 상태를 바꿀 수 있는 방법이 있기 때문에 반드시 가변 객체를 새로 만들라는 보증이 없기 때문이다. 따라서 사이드 이펙트 발생 여부를 남겨 놓을 수 있는 것이다.


그렇기에 불변 객체로 만들어서 다른 상태값이 필요할 경우 새로운 객체를 만들라고 분명하게 명시하면 사이드 이펙트 발생 여부를 줄일 수 있는 것이다.

 

불변 객체 - 값 변경

불변 객체를 사용하지만 그래도 값을 변경해야 하는 메서드가 필요하면 어떻게 해야할까?

불변 객체가 가진 값을 가지며 새로운 값을 가진 불변 객체를 생성하면 된다.

이렇게 하면 또 새로운 불변객체가 생성되는 것이다.
기존의 불변 객체는 변하지 않고, 기존의 불변 객체의 값과 같은 값을 가진 새로운 불변 객체가 생성되는 것이다.

 

정리

모든 클래스를 불변으로 만드는 것은 아니다.

대부분의 클래스는 값을 변경할 수 있게 만들어진다.

가변 클래스가 더 일반적이고, 불변 클래스는 값을 변경하면 안되는 특별한 경우에 만들어서 사용한다고 생각하면 된다.