자바의 데이터 타입은 크게 기본형과 참조형으로 나눈다.
기본형
int a = 10;
int b = a; //a의 값을 복사해서 대입
b = 20; // b를 변경하더라도 a가 변경되지 않음 why? 값을 복사해서 넣었기 떄문
참조형
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);
System.out.println(b);
System.out.println("변경");
b.setValue("부산");
System.out.println(a);
System.out.println(b);
}
}
기본형과 달리 b를 서울에서 부산으로 바꾸면 a도 부산으로 변경된다.
왜냐하면 a와 b는 동일한 참조값을 가지고 하나의 인스턴스를 바라보고 있기 때문이다.
이렇게 공유 참조를 하게되면 사이드 이펙트가 생긴다.
공유참조 문제를 해결하려면 어떻게 해야할까?
그냥, 처음부터 Address b = new Address("서울"); 이런식으로 애초에 인스턴스를 두개 만드는 방법도 있다.
그러나, Address b = a 이건 맞는 문법이다.
이말은 즉슨, 여러 변수가 하나의 객체를 공유하는 것을 막을 방법이 없다는것이다.
그러면 이러한 공유참조로 인해 발생하는 문제를 어떻게 해결해야할까?
공유참조의 문제는 공유참조의 값을 바꿀때 문제가 생긴다. 고로 애초에 바꾸지 못하게 불변 객체로 만들어보자.
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 + '\'' +
'}';
}
}
내부 값이 변경되지 않기 위해 value 필드를 final로 선언했다.
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("부산") 불가 final이므로
b= new ImmutableAddress("부산");
System.out.println("a = "+a);
System.out.println("b = "+b);
}
}
이제는 b.setValue로 value필드를 변경할 수 없다. 어쩔수없이 새로운 Immutable클래스를 만들고 참조값을 대입하는 수밖에 없다.
예제
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 +
'}';
}
}
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);
}
현재 address는 서울로 동일한 address가 memberA,B의 Address필드에 들어가 있다.
그러나, memberB.getAddress().setValue로 값을 변경하지 못한다. why? value가 final이기 때문 그래서 새로운 ImmutableAddress를 만들어 set으로 바꿔줘야한다.
그렇다면 불변객체인데 변경을 하려면 어떻게해야할까?
변경하고자하는 값으로 새로운 객체를 만들어서 반환한다.
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;
}
}
value는 final로 불변이다. 그러나 여기 value에다가 addValue값을 더하고 싶으면 result를 가지고 새로운 ImmutableObj를 만들어서 반환해준다.
public class ImmutableMain1 {
public static void main(String[] args) {
ImmutableObj obj = new ImmutableObj(20);
obj = obj.add(10);
System.out.println(obj.getValue());
}
}
obj에서 set.value(30)으로 못바꾼다 final이므로 그래서 obj.add메서드를 호출하면 20+10인 30 result값을 가지고 새로운 ImmutableObj객체를 만들어 반환한다.
그러고 obj에다가 대입하면된다.
어? 그러면 ImmutalbeObj가 두개가 만들어지는데 20으로 만든 ImmutableObj는 남아있는거 아니냐? 할수 있다.
맞다.
그래서 GC가 어디에서도 사용되지 않는 인스턴스를 지워준다.