이번 시간에는 불변객체
에 대하 알아보도록 하겠습니다.
불변객체(Immutable Object)란?
불변객체
란 한번 객체가 생성되면, 변하지 않는 객체를 의미합니다. Java의 대표적인 불변객체는 String
이 있습니다.
불변객체와 불변객체가 아닌 것
불변객체
x
public class Team {
private final String teamName;
public Team(String teamName) {
this.teamName = teamName;
}
}
위의 객체는 불변객체입니다. 필드의 접근 제한자는 private이며, final 선언자를 통해 변수를 변경할 수 없도록 제한했습니다. (final 선언자가 부여된 필드의 경우, 생성자에서 최초 1회 초기화가 가능합니다.)
불변객체가 아닌 것들
아래의 객체들은 모두 불변객체가 아닙니다.
x
public class People {
public String name;
}
위의 객체의 경우 필드의 접근제한자가 public이기 때문에, 외부의 변경으로부터 자유롭지 못합니다.
public class Account {
private String accountNum;
public void setAccountNum(String newAccountNum) {
this.accountNum = newAccountNum;
}
}
위의 객체의 경우 setter메소드를 통해 accountNum을 변경할 수 있습니다.
왜 불변객체를 만들까?
불변객체에 대해 알고나면, 가장 먼저드는 생각이 있습니다. 왜 불변객체를 만들까?.. , 웹 서핑을 해보면, 아래와 같은 이유들을 자주 볼 수 있습니다.
- 다중 스레드 환경에서 안전하다.
- 방어적 복사본을 만들 필요가 없다.
- 사이드 이펙트가 발생할 확률이 적다.
제 얕은 식견으로는, 정말 이해가 가질 않았습니다. 하지만, 조금만 고민해본다면, 그다지 어려운 얘기도 아니었습니다. 바로 예제를 통해 설명을 드리겠습니다.
불변객체를 사용하지 않은 경우
왜 사용하는지를 알기 위해서, 반대로 사용하지 않은 경우의 불편한 점을 알아보면 조금 더 와닿을 수 있습니다.
x
public class People {
private final PeopleInformation peopleInformation;
public People(PeopleInformation information) {
this.peopleInformation = information;
}
}
public class PeopleInformation {
private String name;
private int age;
public PeopleInformation(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age= age;
}
}
위의 People
는 불변객체일까요? 아쉽지만, 불변객체가 아닙니다. PeopleInformation
참조변수가, final
로 선언되었고, setter 메소드
역시 존재하지 않음에도 왜 불변객체가 아닐까요? 이유는 참조변수인 PeopleInformation
에 있습니다. People
객체가 포함하고 있는, PeopleInformation 객체가 불변객체가 아니기 때문입니다.
문제점
그렇다면 어떤 문제점을 안고 있을까요? People 객체의 문제점을 살펴보도록 합시다.
xxxxxxxxxx
public static void main(String[] args) {
PeopleInformation information = new PeopleInformation("galid", 20);
People p1 = new People(information);
information.setName("lucifer");
information.setAge(30);
People p2 = new People(information);
}
우선, PeopleInformation 객체에는 galid
라는 이름과, 20
이라는 나이가 부여되었고, p1
객체의 인자로 전달했습니다.
이 후, p2
객체에서 PeopleInformation 객체를 재사용하기 위해, setter메소드를 이용해, 원하는 데이터로 변경한 뒤, p2
의 인자로 전달했습니다.
== Before ==
P1 : 20 ,galid
== After ==
P1 : 30 ,lucifer
P2 : 30 ,lucifer
결과는 어떨까요? p1
객체는 변화를 원하지 않았음에도 데이터가 변경되었습니다. 바로 이런 문제점으로 인해, 불변객체는 데이터를 확신할 수 없게 됩니다.
그림으로 다시한번 살펴보겠습니다. p1
은 PeopleInformation("galid", 20)
객체를 참조합니다.
p2
가 나타나 p1
이 사용하는 객체를 재사용하기 위해, 원하는 대로 데이터를 변경합니다. 이때, p1
은 같은 객체를 참조하고 있기 때문에, 데이터가 같이 변경됩니다.
방어적 복사본
그렇다면, 인터넷에 그토록 쓰여있는, 방어적 복사본이란 무엇일까요? 바로 위 PeopleInformation
객체처럼, 다른 객체에서 고루 사용될 가능성이 있는 객체가 변경될 수 있기 때문에, 객체 내부적으로 새로운 객체
를 만들어 반환하도록 만든 코드를 의미합니다.
예제를 통해 알아보도록 합시다.
public class Money {
private int value;
public Money(int value) {
this.value = value;
}
public Money addMoney(int additionValue) {
return new Money(value + additionValue);
}
public int getValue() {
return value;
}
}
위의 Money 클래스는 방어적 복사본을 통해 객체의 변경을 방지한, 클래스의 예입니다. 한번 사용해보죠,
public class MainTest {
public static void main(String[] args) {
Money m1 = new Money(100);
System.out.println("m1 : " + m1.getValue());
Money m2 = m1.addMoney(300);
System.out.println("m2 : " + m2.getValue());
System.out.println("After m1 : " + m1.getValue());
}
}
m1
이 100을 가지고 생성이 되었습니다. m2
가 m1
의 값에 300을 더한 값을 자신의 value
로 사용하려고 하네요, 저런, 또 m1
의 값이 변경되지 않을까요?..
xxxxxxxxxx
=== 결과 ===
m1 : 100
m2 : 400
After m1 : 100
아닙니다, m1
의 값은 유지가 됩니다. 바로 방어적 복사본을 이용했기 때문이죠, Money
클래스는 이 클래스를 통해 생성되는 객체가 다시 이용될 것을 미리 예측하고, addMoney()
메소드의 결과로 새로운 객체를 생성하여 전달했기 때문입니다.
결론
불변객체
는 위와같이 방어적 복사본을 만들어야하는 수고를 덜어주며, 다중스레드 환경에서도 안전합니다.
'Application Knowhow > Common' 카테고리의 다른 글
ApplicationKnowhow - 깔끔한 코드작성 방법 - Class편 (0) | 2019.09.29 |
---|