☕️[Java] 불변 객체 - 도입.
1️⃣ 불변 객체 - 도입.
- 공유하면 안되는 객체를 여러 변수에서 공유하기 때문에 문제가 발생했었습니다.
- 그렇다고 객체의 공유를 막을 수 있는 방법은 없습니다.
- 그러나 사이드 이펙트의 더 근본적인 원인을 고려해보면, 객체를 공유하는 것 자체는 문제가 아닙니다.
- 객체를 공유한다고 바로 사이드 이펙트가 발생하지 않습니다.
- 문제의 직접적인 원인은 공유된 객체의 값을 변경한 것에 있습니다.
- 그렇다고 객체의 공유를 막을 수 있는 방법은 없습니다.
-
"a"
,"b"
는 처음 시점에는 둘 다"서울"
이라는 주소를 사용해야 합니다.- 그리고 이후에
"b"
의 주소를"부산"
으로 변경해야 합니다.Address a = new Address("서울"); Address b = a;
- 그리고 이후에
- 따라서 처음에는
"b = a"
와 같이"서울"
이라는"Address"
인스턴스를"a"
,"b"
가 함께 사용하는 것이, 다음 코드와 같이 서로 다른 인스턴스를 사용하는 것 보다 메모리와 성능상 더 효율적입니다.- 인스턴스가 하나이니 메모리가 절약되고, 인스턴스를 하나 생성하지 않아도 되니 생성 시간이 줄어서 성능상 효율적입니다.
Address a = new Address("서울"); Address b = new Address("서울");
- 인스턴스가 하나이니 메모리가 절약되고, 인스턴스를 하나 생성하지 않아도 되니 생성 시간이 줄어서 성능상 효율적입니다.
- 여기까지는
"Address b = a"
와 같이 공유 참조를 사용해도 아무런 문제가 없습니다.- 오히려 더 효율적입니다.
-
진짜 문제는 이후에 b가 공유 참조하는 인스턴스의 값을 변경하기 때문에 발생합니다.
b.setValue("부산"); // b의 값을 부산으로 변경해야함 System.out.println("부산 -> b"); System.out.println("a = " + a); // 사이드 이펙트 발생 System.out.println("b = " + b);
- 자바에서 여러 참조형 변수가 하나의 객체(인스턴스)를 참조하는 공유 참조 문제는 피할 수 없습니다.
- 기본형과 다르게 참조형인 객체는 처음부터 여러 참조형 변수에서 공유될 수 있도록 설계되었습니다.
- 따라서 이것은 문제가 아닙니다.
-
문제의 직접적인 원인은 공유될 수 있는
Address
객체의 값을 어디선가 변경했기 때문입니다.- 만약
"Address"
객체의 값을 변경하지 못하게 설계했다면 이런 사이드 이펙트 자체가 발생하지 않을 것입니다.
- 만약
-
문제의 직접적인 원인은 공유될 수 있는
- 따라서 이것은 문제가 아닙니다.
- 기본형과 다르게 참조형인 객체는 처음부터 여러 참조형 변수에서 공유될 수 있도록 설계되었습니다.
2️⃣ 불변 객체 도입.
- 객체의 상태(객체 내부의 값, 필드, 멤버 변수)가 변하지 않는 객체를 불변 객체(Immutable Object)라 합니다.
-
"Address"
클래스를 상태가 변하지 않는 불변 클래스로 만들어 보겠습니다.
-
public class ImmutableAddress {
private final String value;
public ImmutableAddress(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return "Address{" +
"value='" + value + '\'' +
'}';
}
}
- 내부 값이 변경되면 안됩니다.
- 따라서
"value"
의 필드를"final"
로 선언했습니다. - 값을 변경할 수 있는
"setValue()"
를 제거했습니다.- 이 클래스는 생성자를 통해서만 값을 설정할 수 있고, 이후에는 값을 변경하는 것이 불가능합니다.
- 따라서
- 불변 클래스를 만드는 방법은 아주 단순합니다.
- 어떻게든 필드 값을 변경할 수 없게 클래스를 설계하면 됩니다.
```java
public class RefMain2 {
public static void main(String[] args) {
ImmutableAddress a = new ImmutableAddress(“서울”);
ImmutableAddress b = a; // 참조값 대입을 막을 수 있는 방법이 없다.
System.out.println(“a = “ + a);
System.out.println(“b = “ + b);// b.setValue("부산"); // 컴파일 오류 발생 b = new ImmutableAddress("부산"); System.out.println("부산 -> b"); System.out.println("a = " + a); // 사이드 이팩트 발생 System.out.println("b = " + b); } } ```
- 어떻게든 필드 값을 변경할 수 없게 클래스를 설계하면 됩니다.
-
"ImmutableAddress"
의 경우 값을 변경할 수 있는"b.setValue()"
메서드 자체가 제거되었습니다.- 이제
"ImmutableAddress"
인스턴스의 값을 변경할 수 있는 방법은 없습니다.-
"ImmutableAddress"
를 사용하는 개발자는 값을 변경하려고 시도하다가, 값을 변경하는 것이 불가능하다는 사실을 알고, 이 객체가 불변 객체인 사실을 깨닫게 됩니다.- 예를 들어
"b.setValue("부산")"
을 호출하려고 했는데, 해당 메서드가 없다는 사실을 컴파일 오류를 통해 인지합니다.- 따라서 어쩔 수 없이 새로운
"ImmutableAddress("부산")"
인스턴스를 생성해서"b"
에 대입합니다.- 결과적으로
"a"
,"b"
는 서로 다른 인스턴스를 참조하고,"a"
가 참조하던"ImmutableAddress"
는 그대로 유지됩니다.
- 결과적으로
- 따라서 어쩔 수 없이 새로운
- 예를 들어
-
- 이제
실행 결과
a = Address{value='서울'}
b = Address{value='서울'}
부산 -> b
a = Address{value='서울'}
b = Address{value='부산'}
- 실행 결과를 보면
"a"
의 값은 그대로 유지되는 것을 확인할 수 있습니다.
- 자바에서 객체의 공유 참조는 막을 수 없습니다.
-
"ImmutableAddress"
는 불변 객체입니다.- 따라서 값을 변경할 수 없습니다.
-
"ImmutableAddress"
은 불변 객체이므로"b"
가 참조하는 인스턴스의 값을 서울에서 부산으로 변경하려면 새로운 인스턴스를 생성해서 할당해야 합니다.
3️⃣ 정리
-
불변이라는 단순한 제약을 사용해서 사이드 이펙트라는 큰 문제를 막을 수 있습니다.
- 객체의 공유 참조는 막을 수 없습니다.
- 그래서 객체의 값을 변경하면 다른 곳에서 참조하는 변수의 값도 함께 변경되는 사이드 이펙트가 발생합니다.
- 사이드 이펙트가 발생하면 안되는 상황이라면 불변 객체를 만들어서 사용하면 됩니다.
- 불변 객체는 값을 변경할 수 없기 때문에 사이드 이펙트가 원천 차단됩니다.
- 불변 객체는 값을 변경할 수 없습니다.
- 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 합니다.
- 이렇게 하면 기존 변수들이 참조하는 값에는 영향을 주지 않습니다.
- 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 합니다.
- 객체의 공유 참조는 막을 수 없습니다.
🙋♂️ 참고 - 가변(Mutable) 객체 vs 불변(Immutable) 객체
가변은 이름 그대로 처음 만든 이후 상태가 변할 수 있다는 뜻입니다.
가변은 사전적으로 사물의 모양이나 성질이 달라질 수 있다는 뜻입니다.
불변은 이름 그대로 처음 만든 이후 상태가 변하지 않는다는 뜻입니다.
불변은 사전적으로 사물의 모양이나 성질이 달라질 수 없다는 뜻입니다.
-
"Address"
는 가변 클래스입니다.- 이 클래스로 객체를 생성하면 가변 객체가 됩니다.
-
"ImmutableAddress"
는 불변 클래스입니다.- 이 클래스로 객체를 생성하면 불변 객체가 됩니다.