오늘의 나보다 성장한 내일의 나를 위해…
Immutable vs Mutable
Mutable: 변할 수 있는; 잘 변하는
Immutable: 변경할 수 없는; 불변의
무엇이 변하고 무엇이 변하지 않는 걸까?
값? 주소?
정의를 하자면
Mutable은 값을 수정해도 주소가 바뀌지 않는 것이다.
Immutable은 값을 수정하면 주소가 바뀌는 것을 말한다.
이렇게 말하면 앞서 말한 영어 정의랑 헷갈린다.
다시 말하면 Immutable 객체는 객체 내의 특정 요소의 값을 변경 할 수 없는 객체이다.
or
문자열 연산에서 new 연산을 통해 생성된 인스턴스의 메모리 공간이 절대 변하지 않는 것이다.
즉, 여기서 변하지 않는다는 것은 객체가 생성되면 변하지 않는다==객체를 재할당 받는다 와 같은 말이다.
Immutable은 대표적으로 String이 있다.
String 변수는 한번 할당하고 그 변수에 새로운 문자열을 합치거나 변경할 때 새로운 주소값이 생기면서 객체가 할당된다.
Example
String result = "Hello";
result = result.concat("World");
위 사진처럼 주소값이 다른 이유는 String의 concat은 new String()으로 새로운 객체를 만들어 할당받기 때문이다.
Example
String userName = "홍길동";
System.out.println(userName);
userName = "둘리";
System.out.println(userName);
// result
홍길동
둘리
위 예제에서도 userName은 홍길동이라는 값을 가지고 1000번 주소에 할당되었지만 값이 변경되면 1000번을 가리키고 있던 것을 새로운 객체가 있는 2000번으로 변경한다.
반면에 대표적인 Mutable 개체로 StringBuilder가 있다. (List, ArrayList, HashMap 등도 Mutable)
Example
StringBuilder result = new StringBuilder();
result.append("Hello");
result.append("World");
위 에서는 변수의 문자열이 추가되어도 주소가 변하지 않는 것을 볼 수 있다.
Immutable한 클래스에는 어떤 것이 있을까?
- String, Boolean, Integer, Float, Long 등이 있다.
immutable한 클래스를 만들어 보자.
immutable은 값을 변경할 수 없는 클래스를 뜻한다. 즉 setter 메서드가 없다. 또한 final 키워드를 사용해 변수 초기화 이후 바뀌지 않도록 막는다.
Example
public class ImmutableTest {
private final String userName;
ImmutableTest(String userName){
this.userName = userName;
}
@Override
public String toString(){
return this.userName;
}
}
String userName = "홍길동";
ImmutableTest immutableString = new ImmutableTest(userName);
userName = new String("둘리");
System.out.println(immutableString);
결과 값은 immutable로 만들었던 userName(홍길동)은 변하지 않고, 새로운 객체 둘리가 userName에 할당된다.
Immutable Object의 장단점
장점
- 객체에 대한 신뢰도가 높아진다. 객체가 한번 생성되어서 그게 변하지 않는다면 transaction 내에서 그 객체가 변하지 않기에 믿고 쓸 수 있기 때문이다.
- 생성자, 접근메소드에 대한 방어 복사가 필요없다.
- 멀티스레드 환경에서 동기화 처리없이 객체를 공유할 수 있다.
단점
- 객체가 가지는 값마다 새로운 객체가 필요하다. 따라서 메모리 누수와 새로운 객체를 계속 생성해야 하기 때문에 성능저하를 발생시킬 수 있다.
Java에서 불변 객체를 생성하기 위한 규칙
- 클래스를 final로 선언하라
- 모든 클래스 변수를 private와 final로 선언하라
- 객체를 생성하기 위한 생성자 또는 정적 팩토리 메서드를 추가하라
- 참조에 의해 변경가능성이 있는 경우 방어적 복사를 이용하여 전달하라
Example
public final class ImmutableClass {
private final int age;
private final String name;
private final List<String> list;
private ImmutableClass(int age, String name) {
this.age = age;
this.name = name;
this.list = new ArrayList<>();
}
public static ImmutableClass of(int age, String name) {
return new ImmutableClass(age, name);
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public List<String> getList() {
return Collections.unmodifiableList(list);
}
}
위의 코드에서 특히 주목해야 하는 부분은 내부 생성자를 만드는 대신 객체의 생성을 위해 정적 팩토리 메소드를 제공하고 있다는 점과 참조를 전달하여 클라이언트에 의해 수정 가능성이 있는 list를 방어적 복사하여 제공하고 있다는 것이다.
Java에서는 생성자를 선언하지 않으면 기본 생성자가 자동으로 생성되는데, 그러면 다른 클래스에서 해당 객체를 자유롭게 호출할 수 있다. 그렇기 때문에 내부 생성자를 만드는 대신 정적 팩토리 메소드를 통해 객체를 생성하도록 강요하는 것이 좋다.
또한 배열이나 다른 객체 또는 컬렉션은 참조가 전달되어 수정 가능성이 있다. 그렇기 때문에 참조를 통해 변경이 가능한 경우에는 방어적 복사를 통해 값을 반환해야 한다.