불변 객체
가 뭘까?
불변 객체
는 생성된 후 변경할 수 없는 객체를 말한다.
불변 객체
는 생성되면 그 상태를 수정할 수 없고, 수정하는 모든 시도는 업데이트된 상태를 가진 새로운 객체가 생성된다.
불변 객체
를 만드려면 다음과 같은 규칙을 따르면 된다.
1. 모든 필드를 private 및 final로 선언하여 생성자 외부에서 수정할 수 없도록 한다.
2. 필드에 대한 setter를 제공하지 않는다.
3. 클래스에서 사용되는 모든 가변 객체를 안전하게 캡슐화되도록 한다.
해당 규칙을 따르며 불변 객체
를 만들어보자.
다음 코드와 같이 Money
라는 클래스가 있다.
public class Money {
private int amount;
public Money(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
1번 규칙을 따라 필드를 final
로 선언하면 자연스럽게 setter
가 사라진다.
public class Money {
private final int amount;
public Money(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
}
하지만 3번 규칙은 뭔가 말이 쉽게 와닿지가 않는다.
모든 가변 객체를 캡슐화 한다는 것이 뭘까?
여기 Person
이라는 클래스를 List
로 관리하는 People
이라는 클래스가 있다.
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class People {
private final List<Person> people;
public People(List<Person> people) {
this.people = people;
}
public List<Person> getPeople() {
return people;
}
}
Person
은 불변 객체가 아니지만, People
은 1번, 2번 규칙을 따랐으니 불변 객체
라고 할 수 있을까?
아니다. 불변 객체라고 할 수 없다.
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("1"));
peopleList.add(new Person("2"));
peopleList.add(new Person("3"));
People people = new People(peopleList);
List<Person> foundPeople = people.getPeople();
foundPeople.add(new Person("4"));
다음과 같이 List
필드를 그대로 가져온다면 final
로 선언을 하여도 수정이 가능하다.
즉, setter
를 사용하는 것과 같다.
따라서 다음과 같이 List.copyOf
혹은 Collections.unmodifiableList
메서드를 사용하여 컬렉션의 값을 변경할 수 없도록 해야한다.
public class People {
private final List<Person> people;
public People(List<Person> people) {
this.people = people;
}
public List<Person> getPeople() {
// return List.copyOf(people);
return Collections.unmodifiableList(people);
}
}
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("1"));
peopleList.add(new Person("2"));
peopleList.add(new Person("3"));
People people = new People(peopleList);
List<Person> foundPeople = people.getPeople();
foundPeople.add(new Person("4")); // Exception 발생!
이제 정말 끝일까?
아니다. 아직도 할 일이 남았다.
Person
클래스는 불변 객체
가 아니다.
따라서 People
에서 읽기 전용인 컬렉션을 반환한다고 해도, 컬렉션이 가지고 있는 객체가 변경되면 상태가 변경된다.
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("1"));
peopleList.add(new Person("2"));
peopleList.add(new Person("3"));
People people = new People(peopleList);
List<Person> foundPeople = people.getPeople();
Person person = foundPeople.get(0);
person.setName("4");
System.out.println(people.getPeople()); // [4, 2, 3]
따라서 Person
도 불변 객체로 만들어줘야 People
을 불변 객체
라고 할 수 있다.
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("1"));
peopleList.add(new Person("2"));
peopleList.add(new Person("3"));
People people = new People(peopleList);
List<Person> foundPeople = people.getPeople();
Person person = foundPeople.get(0);
person.setName("4"); // 컴파일 에러!
이제 정말 끝이 보인다.
하지만, 아직도 디테일이 남아있다.
People
의 생성자로 주어지는 List
가 변경된다면?
다음과 같이 불변 객체라고 생각했던 People
의 상태가 변경됐다.
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("1"));
peopleList.add(new Person("2"));
peopleList.add(new Person("3"));
People people = new People(peopleList);
peopleList.add(new Person("4"));
System.out.println(people.getPeople()); // [1, 2, 3, 4]
따라서 People
의 생성자에 new
키워드로 새로운 참조를 만들어 주어야 한다.
public class People {
...
public People(List<Person> people) {
this.people = new ArrayList<>(people);
}
...
}
List<Person> peopleList = new ArrayList<>();
peopleList.add(new Person("1"));
peopleList.add(new Person("2"));
peopleList.add(new Person("3"));
People people = new People(peopleList);
peopleList.add(new Person("4"));
System.out.println(people.getPeople()); // [1, 2, 3]
이렇게 해서 우리는 People
이라는 클래스를 불변 객체
로 만들 수 있다.
따라서 3번 규칙 클래스에서 사용되는 모든 가변 객체를 안전하게 캡슐화되도록 한다.
는 클래스에 있는 필드 뿐만 아니라 해당 필드가 참조하고 있는 모든 객체를 불변
으로 만들이어야 비로소 불변 객체
라고 부를 수 있다.
또한 클래스에도 final
키워드를 붙여, 상속을 금지하여 재정의까지 금지해야 예상치 못한 상태의 변경을 막을 수 있다.
이렇게 불변 객체의 정의와 만드는 방법을 간단하게 알아봤다.