Java mutable 과 immutable
Mutable 과 Immutable
Java에는 가변(mutable)객체와 불변(immutable)객체가 존재한다.
가변 객체는 힙 영역에 생성된 객체를 변경할 수 있는 객체이고 불변 객체는 반대로 힙 영역에 생성된 객체를 변경할 수 없는 객체이다. 대표적인 불변 객체인 String 으로 예시를 보면 다음과 같다.
String val1 = "abc";
System.out.println("val1 initial " + System.identityHashCode(val1));
val1 += "xyz";
System.out.println("val1 after "+System.identityHashCode(val1));
// output
val1 initial 1435804085
val1 after 1798286609
위와 같이 val1이라는 String 을 선언하고 처음 "abc"로 초기화 한 후에 hascode를 출력해보고 val1에 "xyz"를 추가해준 뒤 다시 한번 hashcode를 출력해보았다. 결과로 나온 output은 두개가 서로 다른 값을 출력하고있다. String은 불변 객체이기에 새로 "abcxyz"라는 String을 생성한 것이다.
Immutable Class
불변 객체를 생성하기위한 규칙은 다음과 같다.
- 모든 필드를 private,final로 선언한다.
- class를 final로 선언한다
- setter method를 제공하지 않는다.
- 참조에 의해 변경 가능성이 있는 경우 방어적 복사를 이용하여 전달한다.
- 객체 생성을 위한 생성자 or 정적 팩토리를 추가한다.
Immutable Class 만들기
public class Immutable {
public int val;
public List<String> list;
public Immutable(int val, List<String> list){
this.val = val;
this.list = list;
}
public int getVal(){
return val;
}
public List<String> getList(){
return list;
}
}
위와같은 class 를 Immutable class로 바꾸어보자.
Immutable immutable = new Immutable(0,new ArrayList<>());
System.out.println(immutable.getVal());
System.out.println(immutable.getList());
immutable.val = 10;
System.out.println(immutable.getVal());
// output
0
[]
10
현재는 위처럼 객체를 하나 만들고 내부 값을 변경시킬 수 있다.
public final class Immutable {
private final int val;
private final List<String> list;
public Immutable(int val, List<String> list){
this.val = val;
this.list = list;
}
public int getVal(){
return val;
}
public List<String> getList(){
return list;
}
}
우선 내부 필드들을 private final로 수정했다. 이럴경우 위의 예시의 immutable.val 처럼 접근하여 값을 바꾸는것은 불가능해진다.
Immutable immutable = new Immutable(0,new ArrayList<>());
List<String> list = immutable.getList();
System.out.println(immutable.getList());
list.add("hello");
System.out.println(immutable.getList());
// output
[]
[hello]
하지만 위의 예시처럼 list같은경우 reference를 참조히기때문에 여전히 내부 값을 수정할 수 있다. 이를 해결하기 위해 방어적 복사를 이용하여 getList를 수정해보자.
public List<String> getList(){
return new ArrayList<>(list);
}
getList에서 반환되는 List를 새로 생성하여 반환해 주었다.
ArrayList<String> initList = new ArrayList<>();
initList.add("hello");
Immutable immutable = new Immutable(0,initList);
List<String> list = immutable.getList();
System.out.println(immutable.getList());
list.add("world");
System.out.println(immutable.getList());
System.out.println(list);
// output
[hello]
[hello]
[hello, world]
getList에서 List를 새로 생성해서 반환했기때문에 immutable.getList() 한 list에 새로 값을 넣어도 내부 값은 변경되지 않았다. 하지만 list.add("world") 처럼 받아온 list에 값은 추가가 되는데, getList를 다음과 같이 변경하면 받아온 list에서의 값 변경도 불가능하게 한다.
public List<String> getList(){
return Collections.unmodifiableList(list);
}