주제 : 김영한님의 자바 중급 1편 총 정리
내용 : 불변 객체 대해 공부
자바의 데이터 타입을 크게 보면 기본형과 참조형으로 나눌 수 있다.
public class PrimitiveMain { public static void main(String[] args) { //기본형은 절대로 같은 값을 공유하지 않는다. int a = 10; int b = a; // a -> b, 값 복사 후 대입 System.out.println("a = " + a); System.out.println("b = " + b); b = 20; System.out.println("20 -> b"); System.out.println("a = " + a); System.out.println("b = " + b); } }
실행 결과
a = 10 b = 10 20 -> b a = 10 b = 20
public class Address { private String value; public Address(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return "Address{" + "value='" + value + '\'' + '}'; } }
public class RefMain1_1 { public static void main(String[] args) { // 참조형 변수는 하나의 인스턴스를 공유할 수 있다. Address a = new Address("서울"); Address b = a; System.out.println("a=" + a); System.out.println("b=" + b); b.setValue("부산"); System.out.println("부산 -> b"); System.out.println("a =" + a); System.out.println("b = " + b); } }
실행 결과를 보면 b 뿐만 아니라 a 의 주소도 함께 부산으로 변경되어 버린다.
참조형 변수는 참조값을 통해 같은 객체(인스턴스)를 공유할 수 있기 때문
a = Address{value='서울'}
b = Address{value='서울'}
부산 -> b
a = Address{value='부산'}
b = Address{value='부산'}
순서대로 코드를 분석해보자.
1. b=a라고 하면 x001에 있는 인스턴스를 a와 b가 같이 참조하게 된다.
Address a = new Address("서울"); Address b = a;
2. 그러한 상황에서 b의 값을 부산으로 변경하면 a의 값도 함께 부산으로 변경되어 버리는 사이드 이펙트가 발생하게 된다. 사이드 이펙트란 특정 부분에서 발생한 병경이 의도치 않게 다른 부분에 영향을 미치는 경우를 의미
b.setValue("부산"); //b의 값을 부산으로 변경해야함 System.out.println("부산 -> b"); System.out.println("a = " + a); //사이드 이펙트 발생 System.out.println("b = " + b);
지금까지 발생한 문제를 잘 생각해보면 공유하면 안되는 객체를 여러 변수에서 공유했기 때문에 발생한 문제이다. 하지만, 앞서 살펴보았듯이 객체의 공유를 막을 수 있는 방법은 없다. 그런데 사이드 이펙트의 더 근본적인 원인을 고려해보면, 객체를 공유하는 것 자체는 문제가 아니다. 문제의 직접적인 원인은 공유된 객체의 값을 변경한 것에 있다.
public class ImmutableAddress { private final String value; // final 사용 public ImmutableAddress(String value) { this.value = value; } public String getValue() { // 값 변경할 수 있는 `setValue()` 제거 return value; } @Override public String toString() { return "ImmutableAddress{" + "value='" + value + '\'' + '}'; } }
- 내부 값이 변경되면 안된다. 따라서
value의 필드를final로 선언했다.- 값을 변경할 수 있는
setValue()를 제거했다.- 이 클래스는 생성자를 통해서만 값을 설정할 수 있고, 이후에는 값을 변경하는 것이 불가능하다.
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 를 사용하는 개발자는 값을 변경하려고 시도하다가, 값을 변경하는 것이 불가능하다는 사실을 알고, 이 객체가 불변 객체인 사실을 깨닫게 된다.ImmutableAddress("부산") 인스턴스를 생성해서 b 에 대입한다.a , b 는 서로 다른 인스턴스를 참조하고, a 가 참조하던 ImmutableAddress 는 그대로 유지된다.실행결과
a = Address{value='서울'}
b = Address{value='서울'}
부산 -> b
a = Address{value='서울'}
b = Address{value='부산'}
조금 더 복잡하고 의미있는 예제를 통해서 불변 객체의 사용 예제 확인
public class MemberV2 { private String name; private ImmutableAddress address; public MemberV2(String name, ImmutableAddress address) { this.name = name; this.address = address; } public String getName() { return name; } public ImmutableAddress getAddress() { return address; } public void setAddress(ImmutableAddress address) { this.address = address; } @Override public String toString() { return "Member{" + "name='" + name + '\'' + ", address=" + address + '}'; } }
MemberV2는 주소를 변경할 수 없는, 불변인ImmutableAddress를 사용한다.
public class MemberMainV2 { public static void main(String[] args) { ImmutableAddress address = new ImmutableAddress("서울"); MemberV2 memberA = new MemberV2("회원A", address); MemberV2 memberB = new MemberV2("회원B", address); //회원A, 회원B의 처음 주소는 모두 서울 System.out.println("memberA = " + memberA); System.out.println("memberB = " + memberB); //회원B의 주소를 부산으로 변경해야함 //memberB.getAddress().setValue("부산"); //컴파일 오류 memberB.setAddress(new ImmutableAddress("부산")); System.out.println("부산 -> memberB.address"); System.out.println("memberA = " + memberA); System.out.println("memberB = " + memberB); } }
회원B의 주소를 중간에 부산으로 변경하려고 시도한다.- 하지만
ImmutableAddress에는 값을 변경할 수 있는 메서드가 없다. 따라서 컴파일 오류가 발생한다.- 결국
memberB.setAddress(new ImmutableAddress("부산"))와 같이 새로운 주소 객체를 만들어서 전달한다.
새롭게 생성한 객체를 반환한다 (중요!!!!)
불변 객체를 사용하지만 그래도 값을 변경해야 하는 메서드가 필요한 경우가 존재한다. 예를 들어서, 기존 값에 새로운 값을 더하는 add()와 같은 메서드가 있다. 먼저, 변경 가능한 객체에서 값을 변경하는 예를 만들어보자.
public class MutableObj { private int value; public MutableObj(int value){ this.value = value; } public void add(int addValue){ value = value + addValue; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } }public class MutableMain { public static void main(String[] args) { MutableObj obj = new MutableObj(10); obj.add(20); // 계산 이후 기존 값은 사라짐 System.out.println("obj = " + obj.getValue()); } }
MutableObj 을 10 이라는 값으로 생성한다.obj.add(20) 을 통해서 10 + 20 을 수행한다.10 이라는 값은 사라진다.MutableObj 의 상태(값)가 10 30 으로 변경되었다.obj.getValue() 를 호출하면 30 이 출력된다이번에는 불변 객체에서 add() 메서드를 어떻게 구현하는지 알아보자
-> 결과를 바탕으로 새로운 객체를 만드는 것이 핵심이다!!!!
public class ImmutableObj { private final int value; public ImmutableObj(int value){ this.value = value; } public ImmutableObj add(int addValue){ int result = value + addValue; return new ImmutableObj(result); // 여기!!!! 중요!!!! } public int getValue(){ return value; } }
add() 메서드이다.public class ImmutableMain1 { public static void main(String[] args) { ImmutableObj obj1 = new ImmutableObj(10); ImmutableObj obj2 = obj1.add(20); //계산 이후에도 기존값과 신규값 모두 확인 가능 System.out.println("obj1 = " + obj1.getValue()); System.out.println("obj2 = " + obj2.getValue()); } }
실행 결과
obj1 = 10
obj2 = 30
불변 객체를 설계할 때 기존 값을 변경해야 하는 메서드가 필요할 수 있다. 이때는 기존 객체의 값을 그대로 두고 대신에 변경된 결과를 새로운 객체에 담아서 반환하면 된다. 결과를 보면 기존 값이 그대로 유지되는 것을 확인할 수 있다.

add(20) 을 호출한다.10 과 인수로 입력한 20 을 더한다. 이때 기존 객체의 값을 변경하면 안되므로 계산 결과를 기반으로 새로운 객체를 만들어서 반환한다.x002 참조를 가진다. 새로운 객체의 참조값을 obj2 에 대입한다.[문1] 불변 객체
팁: 멤버 변수 final, 메서드 설정 잘하기
MyDate 클래스는 불변이 아니어서 공유 참조시 사이드 이펙트가 발생한다. 이를 불변 클래스로 만들어라.ImmuableMyDate 로 이름 지으면 된다.ImmuableMyDateMain 으로 이름 지으면 된다.