불변 객체(Immutable Object)는 한 번 생성되면 내부 상태가 변경되지 않는 객체이다.
불변 객체를 사용하는 이유와 사용 방법을 자세히 알아보자.
기본형(Primitive Type): 하나의 값을 여러 변수에 절대로 공유하지 않음
b
에 a
를 대입할 때, 자바는 값을 복사해서 대입한다.b
값만 5로 변경하면, a
의 값은 변경되지 않는다.public class PrimitiveMain {
public static void main(String[] args) {
int a = 3;
int b = a;
System.out.println("a = " + a);
System.out.println("b = " + b);
b = 5;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
a = 3
b = 3
a = 3
b = 5
참조형(Reference Type): 하나의 객체를 참조값을 통해 여러 변수에서 공유할 수 있음
addressA
을 만들어주었다. 그리고 addressB
에 addressA
을 대입하는 코드를 작성해주었다. 그러면 addressA
과 addressB
는 Hong이라는 이름을 가진다.addressB
만 이름을 Kim으로 변경해주었다. 실행 결과를 보면, 기본형과 다르게 addressA
의 이름도 Kim으로 변경된 것을 볼 수 있다.addressA
과 addressB
가 같은 참조값을 가지고 있기 때문에 같은 결과가 나오는 것이다.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 AddressMain {
public static void main(String[] args) {
Address addressA = new Address("Seoul");
Address addressB = new Address("Seoul");
System.out.println("addressA = " + addressA);
System.out.println("addressB = " + addressB);
addressB.setValue("Changwon");
System.out.println("addressA = " + addressA);
System.out.println("addressB = " + addressB);
}
}
addressA = Address{value='Seoul'}
addressB = Address{value='Seoul'}
addressA = Address{value='Changwon'}
addressB = Address{value='Changwon'}
AddressMain
코드에서 addressB
만 변경하였지만, addressA
도 함께 변경된 문제가 있었다.Address addressA = new Address("Seoul");
Address addressB = new Address("Changwon");
이렇게 서로 다른 객체를 참조하게 하면 문제가 해결된다. 하지만, 객체를 공유하는 것을 강제로 막을 방법이 없다.
그래서 객체의 상태(객체 내부 값, 필드, 멤버 변수)가 변하지않는 불변 객체를 사용한다.
"final
로 선언하면 되지 않나?"라고 생각할 수 있다.
맞다. 그러면 setValue() 메서드를 제거해야하니까, 값 변경이 아예 불가능해진
다.
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 + '\'' +
'}';
}
}
public class AddressMain2 {
public static void main(String[] args) {
ImmutableAddress addressA = new ImmutableAddress("Seoul");
ImmutableAddress addressB = addressA;
System.out.println("addressA = " + addressA);
System.out.println("addressB = " + addressB);
addressB = new ImmutableAddress("Changwon");
System.out.println("addressA = " + addressA);
System.out.println("addressB = " + addressB);
}
}
member1 = Member{value='Kim'}
member2 = Member{value='Kim'}
member1 = Member{value='Kim'}
member2 = Member{value='Hong'}
가변 객체(Mutable Object) vs. 불변 객체(Immutable Object)
가변은 처음 만든 이후, 상태가 변할 수 있다는 의미
불변은 처음 만든 이후, 상태가 변하지 않는 다는 의미
Address
Member1
에서 Address
를 사용하여 주소를 저장하도록 하였다.MemberMain1
에서 memberB
의 주소를 "Changwon"으로 변경하니까 memberA
의 주소도 함께 변경되었다.public class Member1 {
private String name;
private Address address;
public MemberV(String name, Address address) {
this.name = name;
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Member1{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
}
public class MemberMain1 {
public static void main(String[] args) {
Address address = new Address("Seoul");
Member1 memberA = new Member1("Kim", address);
Member1 memberB = new Member1("Hong", address);
System.out.println("memberA = " + memberA);
System.out.println("memberB = " + memberB);
memberB.getAddress().setValue("부산");
System.out.println("memberA = " + memberA);
System.out.println("memberB = " + memberB);
}
}
memberA = Member{name='Kim', address=Address{value='Seoul'}}
memberB = Member{name='Hong', address=Address{value='Seoul'}}
memberA = Member{name='Kim', address=Address{value='Changwon'}}
memberB = Member{name='Hong', address=Address{value='Changwon'}}
Immutable Address
Member2
에서 Immutable Address
를 사용하여 주소를 저장하도록 하였다.MemberMain2
에서 memberB
의 주소를 새로운 Immutable Address
객체를 생성하여 변경하였다. 이렇게 하니까 memberA
의 주소는 변경되지 않았다.public class Member2 {
private String name;
private ImmutableAddress address;
public Member2(String name, ImmutableAddress address) {
this.name = name;
this.address = address;
}
public ImmutableAddress getAddress() {
return address;
}
public void setAddress(ImmutableAddress address) {
this.address = address;
}
@Override
public String toString() {
return "Member2{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
}
public class MemberMain2 {
public static void main(String[] args) {
ImmutableAddress address = new ImmutableAddress("Seoul");
Member2 memberA = new Member2("Kim", address);
Member2 memberB = new Member2("Hong", address);
System.out.println("memberA = " + memberA);
System.out.println("memberB = " + memberB);
memberB.setAddress(new ImmutableAddress("Changwon"));
System.out.println("memberA = " + memberA);
System.out.println("memberB = " + memberB);
}
}
memberA = Member{name='Kim', address=Address{value='Seoul'}}
memberB = Member{name='Hong', address=Address{value='Seoul'}}
memberA = Member{name='Kim', address=Address{value='Seoul'}}
memberB = Member{name='Hong', address=Address{value='Changwon'}}
불변 객체는 값을 변경하면 더이상 불변 객체가 아니다. 그렇다면, 기존 값에 새로운 값을 더하는 add()
와 같은 메서드는 어떻게 구현할 수 있을까?
value
)을 변경하지 않고, 계산 결과(result
)를 바탕으로 새로운 객체를 생성하여 반환(return new ImmutableObj(result)
)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;
}
}
public class ImmutableMain {
public static void main(String[] args) {
ImmutableObj objectA = new ImmutableObj(30);
ImmutableObj objectB = objectA.add(50);
System.out.println("objectA = " + objectA.getValue());
System.out.println("objectB = " + objectB.getValue());
}
}
objectA = 30
objectB = 80
final
을 사용하여 불변 객체 만들 수 있음