[Java] 불변 객체
자바에서 데이터 타입은 크게 기본형과 참조형으로 나뉜다.
기본형: 하나의 값을 여러 변수에서 공유하지 않는다.
참조형: 하나의 객체를 참조값을 통해 여러 변수가 공유한다.
기본형 값 복사
package immutableBlog;
public class CallByValue {
public static void main(String[] args) {
int a = 7;
int b = a;
System.out.println("a = " + a);
System.out.println("b = " + b);
b = 300;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
기본형은 b=a 연산을 해도 하나의 변수를 쓰는 것이 아니다. 다시 말해 값을 공유하는 것이 아닌 a의 값을 복사하여 대입한다.
결과적으로 a, b는 복사 대입했을 때는 둘 다 7이라는 정수값을 가지고 있지만 메모리상 a에 속하는 7과 b에 속하는 7이 각 각 별도로 존재한다.
참조형 값 복사
package blogimmutability;
public class Reference {
public static void main(String[] args) {
Address myAddress = new Address("서울");
Address yourAddress = myAddress; //같은 곳 산다고 가정
System.out.println("myAddress: " + myAddress.getAddress());
System.out.println("yourAddress: " + yourAddress.getAddress());
System.out.println("yourAddress 이사: 부산으로");
yourAddress.setAddress("부산");
System.out.println("myAddress: " + myAddress.getAddress());
System.out.println("yourAddress: " + yourAddress.getAddress());
}
}
class Address {
private String address;
public Address(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
myAddress와 yourAddress는 같은 주소값(참조값)을 지니고 있다. 따라서 어느 한 곳에 수정이 일어나면 두 곳 전부 같은 곳을 참조하고 있으므로 수정된 값을 공유하는 것이다.
즉, 참조형 복사는 원본 데이터에 영향이 갈 수 있다.
사이드 이펙트(Side Effect)
참조 값을 공유하게 되면 사이드 이펙트(Side Effect)가 발생할 수 있다. 사이드 이펙트는 프로그래밍에서 어떤 계산이 주된 작업 외에 추가적인 부수 효과를 일으키는 것을 말한다.
위 코드에서 yourAddress 만 "부산"으로 값을 변경하였지만 myAddress도 문자열값이 변경되었다.
개발자가 의도해서 변경한거면 상관없지만 의도치 않게 다른 부분에 영향을 미치며 인지하지 못할 시 디버깅이 어려워지고 안정성이 저하된다.
사이드 이펙트 해결법 1
단순하게 참조값을 복사, 공유를 안 하면 된다.
Address myAddress = new Address("서울");
Address yourAddress = new Address("서울"); //같은 곳 산다고 가정
두 객체는 서로 다른 참조값을 가지고 있다.
객체 생성을 서로 다르게 하고 독립적으로 값을 수정하면 된다.
사이드 이펙트 해결법 2
같은 객체를 공유하지 않으면 사이드 이펙트는 발생하지 않는다. 하지만 JVM에서는 사이드 이펙트가 큰 에러가 아니다. 오히려 참조값 공유를 JVM이 막지 않는다. 이러한 이유로 프로그래밍 중 실수로 참조값을 공유하게 된다면 어떻게 될까?
사이드 이펙트가 발생할 것이다. 이러한 문제를 해결하기 위해 불변 객체를 이용해야 한다.
불변객체
class ImmutableAddress {
//상수 이용
private final String address;
public ImmutableAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
/* address 값이 상수이므로 객체 생성 이 후 값 변경 못함.
public void setAddress(String address) {
this.address = address;
}
*/
}
주소값을 수정 못하게 막으면 된다. 인스턴스 변수 address를 상수로 바꾸어주면 set method로 수정도 못한다. 처음 생성자로 인해 객체 생성된 값을 가지고 프로그램이 끝날 때 까지 해당 객체 인스턴스 변수값은 수정 못한다.
객체 공유 참조는 JVM에서 막지 않으니 객체 공유는 어디서든 일어날 수 있다. 객체 공유를 인지하지 못하면 사이드 이펙트가 발생하는데 사이드 이펙트를 막기 위해서 불변 객체를 만들어 사용한다.
불변 객체는 final 키워드로 인스턴스 변수 값 변경을 막을 수 있다. 값을 변경하고 싶다면 새로운 객체를 생성하는 것 말곤 방법이 없다.
package blogimmutability;
public class Reference {
public static void main(String[] args) {
Address address = new Address("서울");
MemberClass memberClass1 = new MemberClass("customer1", address);
MemberClass memberClass2 = new MemberClass("customer2", address);
System.out.println(memberClass1.toString());
System.out.println(memberClass2.toString());
System.out.println("customer2 주소 변경!! 부산으로");
memberClass2.setAddress(new Address("부산"));
System.out.println(memberClass1.toString());
System.out.println(memberClass2.toString());
}
}
final class MemberClass {
//상수 이용
private String name;
private Address address;
public MemberClass(Address address) {
this.address = address;
}
public MemberClass(String name, Address reference) {
this.name = name;
this.address = reference;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "MemberClass{" +
"이름='" + name + '\'' +
", address=" + address.getAddress() +
'}';
}
}
class Address {
private final String address;
Address(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
}
package blogimmutability;
public class Reference {
public static void main(String[] args) {
Obj1 obj1 = new Obj1(30);
Obj1 obj2 = obj1.add(500);
System.out.println(obj1.getValue());
System.out.println(obj2.getValue());
}
}
class Obj1 {
private final int value;
Obj1(int value) {
this.value = value;
}
public Obj1 add(int value) {
int returnValue = this.value + value;
return new Obj1(returnValue);
}
public int getValue() {
return value;
}
}
해당 코드는 기존의 add 전 정수값과 add 연산 이 후 정수값 둘 다 보존할 수 있는 코드이다.