Mutable, Immutable
* Mutable, Immutable
- 자바는 new 연산자로 객체를 생성할 수 있고, 이때 heap 영역에 할당되고 stack 영역에서 참조 타입 변수를 통해 데이터에 접근한다.
이때 자바의 객체의 타입은 두 가지 있다.
- Mutable (가변) 객체와 Immutable (불변) 객체이다.
* Immutable (불변) 객체
- 정의 : 객체 생성 이후에는 객체의 상태가 바뀌지 않는 객체
- 불변 객체 종류 : String, Boolean, Integer, Float, Long 등...
- String을 제외하고 원시 타입의 wrapper 타입이다
Q. 불변 객체는 왜 사용할까요?
A. 클래스들은 가변적이어야 하는 매우 타당한 이유가 있지 않는 한 반드시 불변으로 만들어야 한다. 만약 클래스를 불변으로 만드는 것이 불가능하다면, 가능한 변경 가능성을 최소화 해야함.
1. 단순하다.
- 불변 객체의 상태는 생성된 시점으로부터 파괴되는 시점까지 그대로 유지된다.
2. 일반적으로 스레드 안정성(Thread Safety) 보장
- multi-thread 환경에서 동기화 문제가 발생하는 이유는 공유 자원에 동시 쓰기 연산 때문이다.
- multi-thread 환경에서 동기화 처리 없이 객체 공유가 가능하다.
3. 값의 변경을 방지
- 불변객체는 생성 시점에 값을 설정한 후, 변경할 수 없기 때문에 예기치 않은 값 변경을 방지할 수 있다.
4. 불변 객체는 객체의 필드로 사용
- 불변 객체를 필드로 사용하면 방어적 복사를 할 필요가 없다.
* String, StringBuilder, StringBuffer
1. String ( Immutable 객체 )
- String은 대표적인 Immutable 객체로 읽을 수만 있고 변경 불가능.
2. StringBuilder (Mutable)
- StringBuilder는 단일 스레드 환경에서만 사용하도록 설계되어 있다.
3. StringBuffer (Mutable)
- StringBuffer는 각 메서드 별로 synchronized keyword가 존재하여 멀티 스레드 상태에서 동기화를 지원함.
* 불변 객체 구현
1. 생성자를 제외하고 객체의 상태를 변경하는 메서드 (ex.setter)를 사용하지 않는다.
2. 클래스를 확장할 수 없도록 한다. - final 클래스로 선언 등의 방법으로 상속을 막는다.
3. 모든 필드를 private final로 선언한다.
4. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
- 클래스에 가변 객체를 잠조하는 필드가 하나라도 있다면, 클라이언트가 그 객체의 참조를 그대로 반환받도록 하지 말고
방어적 복사를 수행해야 한다.
* 방어적 복사 vs Unmodifiable Collection
[ 방어적 복사 ]
- 의미 : 생성자의 인자로 받은 객체의 복사본을 만들어 내부 필드를 초기화 하거나, getter 메서드에서 내부의 객체를 반환할때, 객체의 복사본을 반환하는 것을 말한다.
- 장점 : 방어적 복사를 사용하면 외부에서 객체를 변경해도 내부의 객체는 변경되지 않는다.
1. 방어적 복사를 하지 않을 때
- Name 클래스
public class Name {
private final String name;
public Name(String name) {
this.name = name;
}
}
- Names 클래스 (일급 컬렉션) : List<Name>을 가지고 있다.
import java.util.List;
public class Names {
private final List<Name> names;
public Names(List<Name> names) {
this.namse = names;
}
}
2. 방어적 복사를 한 경우
- 기존에는 생성자에서 인자를 받고 바로 초기화했다면
public Names(List<Name> names) {
this.names = names;
}
방어적 복사를 하는 경우에는 생성자에서 인자를 받으면서 new ArrayList<>()를 이용해 만든 복사본으로, 필드 names를 초기화 함.
import.java.util.*;
public class Names {
private final List<Name> names;
public Names(List<Name> names) {
/* 방어적 복사 */
this.names = new ArrayList<>(names);
}
}
- 복사본으로 초기화하여 원본 값과 주소 공유를 끊었기 때문에 더 이상 외부 값 변경에 따라 변하지 않는다.
3. Q.방어적 복사는 깊은 복사일까?
- 아니다. 컬렉션 주소만 바뀌었을 뿐. 내부 요소들은 여전히 주소를 공유하고 있다.
즉, 원본의 내부 요소를 바꾸면 복사본도 바뀌게 된다.
- 그래서 외부로부터의 변경에 취약하지 않도록 객체를 불변으로 만들고자 한다면 내부 요소들 또한 불변이어야 한다.
4. Q.reference 타입이나 collection은 final이면 불변일까?
A. 아니다.
- 위의 예시에서 객체를 생성할 때 방어적 복사를 통해 외부 참조를 끊었다. 또한 names 필드를 private final로 선언하여 재할당이 불가능하다. 또한 현재 상태를 변화시킬 수 있는 로직 또한 없다.
- 재할당이 불가능한 것은 맞지만 불변인 것은 아니다.
List<Name> names = new ArrayList<>();
names.add(new Name("judy"));
names.add(new Name("neo"));
Names baseNames = new Names(names);
List<Name> getNames = baseNames.getNames();
getNames.add(new Name("hash"));
System.out.println(baseNames.toString());
System.out.println(getNames.toString());
[Unmodifiable Collection]
* 결론
- 클래스들은 가변적이어야 하는 매우 타당한 이유가 있지 않는 한 반드시 불변으로 만들어야 한다. 만약 클래스를 불변으로 만드는 것이 불가능하다면, 가능한 변경 가능성을 최소화해야함.