[김영한의 실전 자바 - 중급 1편] 02. 불변 객체

Turtle·2024년 7월 6일
0
post-thumbnail

🙄기본형과 참조형의 공유

public class PrimitiveMain {
	public static void main(String[] args) {
		int a = 10;
		int b = a;

		System.out.println(a);
		System.out.println(b);

		b = 20;
		System.out.println(a);
		System.out.println(b);
	}
}
  • ✔️기본형
    • b = a라고 하면 자바는 항상 값을 복사해서 대입한다.
    • b = 20이라고 하면 b의 값만 20으로 변경된다.
    • a의 값은 10으로 그대로 유지된다.
    • 기본형 변수는 하나의 값을 절대로 공유하지 않는다. 따라서 값을 변경해도 변수 하나의 값만 변경된다.

public class ReferenceMain {
	public static void main(String[] args) {
		Address address1 = new Address("낙원구 행복동");
		Address address2 = address1;

		System.out.println(address1);
		System.out.println(address2);

		address2.setAddress("잘 모르구 기쁩니동");
		System.out.println(address1);
		System.out.println(address2);
	}
}
  • ✔️참조형
    • 변수 address1, address2는 둘 다 같은 주소를 가진다.
    • 이후에 address2의 주소를 변경한다.
    • address2만 변경되길 기대했지만 실제 결과는 address1, address2 둘 다 변경된다.
    • 참조형 변수들은 참조값을 통해 같은 인스턴스를 참조할 수 있다.

🙄공유 참조와 사이드 이펙트

사이드 이펙트(Side Effect)는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 말한다. b의 값을 부산으로 변경한 예시를 다시 분석해보자.

b의 주소값을 변경했으나 a, b는 같은 인스턴스를 참조하기 때문에 a의 값도 같이 변경되어 버린다. 이렇게 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 사이드 이펙트라 한다. 프로그래밍에서 사이드 이펙트는 보통 부정적인 의미로 사용되는데, 사이드 이펙트는 프로그램의 특정 부분에서 발생한 변경이 의도치 않게 다른 부분에 영향을 미치는 경우에 발생한다. 이로 인해 디버깅이 어려워지고 코드의 안정성이 저하될 수 있다.

public class ReferenceMain {
	public static void main(String[] args) {
		Address address1 = new Address("낙원구 행복동");
		Address address2 = address1;

		System.out.println(address1);
		System.out.println(address2);

		change(address2, "잘 모르구 기쁩니동");
		System.out.println(address1);
		System.out.println(address2);
	}

	private static void change(Address address, String stringAddress) {
		System.out.println("주소 값을 변경합니다. → " + stringAddress);
		address.setAddress(stringAddress);
	}
}

실행 결과

Address{address='낙원구 행복동'}
Address{address='낙원구 행복동'}
주소 값을 변경합니다. → 잘 모르구 기쁩니동
Address{address='잘 모르구 기쁩니동'}
Address{address='잘 모르구 기쁩니동'}
  • ✔️사이드 이펙트 해결방안
    • a와 b를 처음부터 서로 다른 인스턴스를 참조하도록 하면 된다.
    • 쉽게 말해 객체를 공유하지 않도록 하는 것이다.

🙄불변 객체 - 도입

사이드 이펙트의 더 근본적인 원인을 고려해보면, 객체를 공유하는 것 자체는 문제가 아니다. 객체를 공유한다고 바로 사이드 이펙트가 발생하진 않는다. 문제의 직접적인 원인은 공유된 객체의 값을 변경하는 것에 있다.

객체의 상태(객체 내부의 값, 필드, 멤버 변수)가 변하지 않는 객체를 불변 객체(Immutable Object)라 한다.

public class ImmutableAddress {
	private final String value;

	public ImmutableAddress(String value) {
		this.value = value;
	}

	public String getValue() {
		return value;
	}

	@Override
	public String toString() {
		return "ImmutableAddress{" +
				"value='" + value + '\'' +
				'}';
	}
}
  • ✔️불변 객체
    • 내부 값이 변경되면 안 된다. 따라서 필드 valuefinal로 선언했다.
    • 값을 변경할 수 있는 setValue()를 제거했다.
    • 생성자를 통해서만 값을 설정할 수 있고 이후에는 값을 변경하는 것이 불가능하다.
public class ImmutableAddressMain {
	public static void main(String[] args) {
		ImmutableAddress immutableAddress1 = new ImmutableAddress("~~구 ~~동");
		ImmutableAddress immutableAddress2 = immutableAddress1;

		// 공유 참조 & 사이드 이펙트 발생
		System.out.println(immutableAddress1);
		System.out.println(immutableAddress2);

		// immutableAddress1.setValue();
		// 불변 객체 사용 & 사이드 이펙트 방지
		immutableAddress2 = new ImmutableAddress("XX구 XX동");
		System.out.println(immutableAddress1);
		System.out.println(immutableAddress2);
	}
}
  • ✔️정리
    • 불변이라는 단순한 제약을 사용해서 사이드 이펙트라는 큰 문제를 막을 수 있다.
    • 객체의 공유 참조는 막을 수 없다. 그래서 객체의 값을 변경하면 다른 곳에서 참조하는 변수의 값도 함께 변경되는 사이드 이펙트가 발생한다. 사이드 이펙트가 발생하면 안되는 상황이라면 불변 객체를 만들어 사용하면 된다. 불변 객체는 값을 변경할 수 없기 때문에 사이드 이펙트가 원천 차단된다.
    • 불변 객체는 값을 변경할 수 없다. 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 된다. 이렇게 하면 기존 변수들이 참조하는 값에는 영향을 주지 않는다.
  • ✔️가변(Mutable) 객체 vs 불변(Immutable) 객체
    • 가변은 이름 그대로 처음 만든 이후 상태가 변할 수 있다는 뜻이다. 불변은 이름 그대로 처음 만든 이후 상태가 변하지 않는다는 뜻이다.

🙄불변 객체 - 예제

public class Member {
	private ImmutableAddress immutableAddress;
	private String name;

	public Member(ImmutableAddress immutableAddress, String name) {
		this.immutableAddress = immutableAddress;
		this.name = name;
	}

	public ImmutableAddress getImmutableAddress() {
		return immutableAddress;
	}

	public void setImmutableAddress(ImmutableAddress immutableAddress) {
		this.immutableAddress = immutableAddress;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Member{" +
				"immutableAddress=" + immutableAddress +
				", name='" + name + '\'' +
				'}';
	}
}
public class MemberMain {
	public static void main(String[] args) {
		ImmutableAddress immutableAddress = new ImmutableAddress("XX구 XX동");

		Member member1 = new Member(immutableAddress, "아무개");
		Member member2 = new Member(immutableAddress, "홍길동");

		System.out.println(member1);
		System.out.println(member2);

		// 홍길동의 주소를 바꿔라.
		member2.setImmutableAddress(new ImmutableAddress("낙원구 행복동"));
		System.out.println(member1);
		System.out.println(member2);
	}
}

실행 결과

Member{immutableAddress=ImmutableAddress{value='XX구 XX동'}, name='아무개'}
Member{immutableAddress=ImmutableAddress{value='XX구 XX동'}, name='홍길동'}
Member{immutableAddress=ImmutableAddress{value='XX구 XX동'}, name='아무개'}
Member{immutableAddress=ImmutableAddress{value='낙원구 행복동'}, name='홍길동'}

🙄불변 객체 - 값 변경

package me.jangwoojin.address;

public class ImmutableObj {
	private final int value;

	public ImmutableObj(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}

	public ImmutableObj add(int addValue) {
		int result = this.value + addValue;
		return new ImmutableObj(result);
	}
}
  • ✔️불변 객체 - 값 변경 핵심
    • 불변 객체는 값을 변경하면 안 된다.
    • final 키워드를 사용해 생성자를 통해서 한 번만 초기화가 될 수 있다.
    • 기존 값에 새로운 값을 더하고 계산 결과를 바탕으로 새로운 객체를 만들어서 반환한다.

0개의 댓글