불변 객체 (Immutable Object)
공유된 참조 객체의 값을 변경하여 발생하는 문제들을 해결 가능
→ 객체가 가지고 있는 필드(멤버 변수)를 변경할 수 없도록 설계
public class ImmutableReference {
private final String name; //final로 선언하여 값 변경 불가
// 생성자만 값설정 가능
public ImmutableReference(String name) {
this.name= name;
}
public String getName() {
return name;
}
// final 키워드로 값의 변경이 불가능하여 해당 메서드 사용불가
// public void setName(String name) {
// this.name = name;
// }
}
- 오직 생성자만 값을 설정 가능
- final 키워드를 사용하여 값 변경 불가
- final을 사용하지 않아도, 값들이 바뀌는것이 불가능하다면 불변 객체임
ㄴ setter를 사용하지 않는 경우
public class ImmutableMain {
public static void main(String[] args) {
ImmutableReference reference1 = new ImmutableReference("황원욱");
ImmutableReference reference2 = reference1; //둘은 같은 주소값 가짐
// stack 영역에 주소값이 저장된다.
// 변경 불가
// reference2.setName("홍정기");
}
}
public class SpartaV2 {
private String id;
private ImmutableReference reference; // 불변 객체 사용
public SpartaV2(String id, ImmutableReference reference) {
this.id = id;
this.reference = reference;
}
public ImmutableReference getImmutableReference() {
return reference;
}
// 불변 객체를 필드로 가지는 다른 클래스에서의 수정 set은 가능.
public void setReference(ImmutableReference reference) {
this.reference = reference;
}
}
- 불변 객체를 필드로 가지는 다른 클래스에서의 set은 가능 (여기서 불변 객체를 final로 선언하지는 않았으므로!)
public class ImmutableMain2 {
public static void main(String[] args) {
// 불변 객체 생성
ImmutableReference reference = new ImmutableReference("황원욱");
SpartaV2 sparta1 = new SpartaV2("1", reference);
SpartaV2 sparta2 = new SpartaV2("2", reference);
// sparta 인스턴스가 가지고 있는 Reference 객체의 값 출력
System.out.println("------- 인스턴스에 저장된 객체 값 확인 -------");
System.out.println("sparta1.referenceName = " + sparta1.getImmutableReference().getName());
System.out.println("sparta2.referenceName = " + sparta2.getImmutableReference().getName());
// sparta2의 Reference에 저장된 값 변경 불가
// sparta2.getReference().setName("홍정기");
// 변경하고 싶다면 새로운 인스턴스를 생성하여 reference 필드 자체를 set으로 변경
// 새로운 "홍정기" 이름의 불변 객체 생성
ImmutableReference reference2 = new ImmutableReference("홍정기");
// SpartaV2 클래스의 인스턴스인 sparta2의 reference 필드 자체는 set으로 변경 가능
sparta2.setReference(reference2);
System.out.println("------- 변경 후 값 확인 -------");
System.out.println("sparta1.referenceName = " + sparta1.getImmutableReference().getName());
System.out.println("sparta2.referenceName = " + sparta2.getImmutableReference().getName());
}
}
- sparta2의 Reference에 저장된 값을 변경하고 싶다면 새로운 불변 객체 인스턴스를 생성하여 sparta2의 reference 필드를 set해야함
불변 객체를 변경해야하는 경우
public class ImmutableReference {
private final String name;
// 생성자
public ImmutableReference(String name) {
this.name= name;
}
public String getName() {
return name;
}
// 새로운 객체를 생성하여 반환 한다.
public ImmutableReference withName(String name) {
return new ImmutableReference("name");
}
}
- 새로운 인스턴스를 생성하여 반환
ㄴ 기존 인스턴스의 값은 그대로 유지됨
- 불변 객체의 값을 변경하는 메서드는 with이름() 형태로 사용하는것이 일반적
ㄴ 기존 인스턴스 + 새로운 인스턴스 라는 의미
String은 불변 객체이다
String은 참조형에 속하지만 기본형처럼 사용
- String 뿐만이 아니라 Integer와 같이 다양한 클래스들이 불변 객체이다.
- 가변 String 객체는 StringBuilder
- String 클래스에는 값을 변경해주는 메소드들이 존재하지만 해당 메소드를 통해 데이터를 바꾼다 해도
새로운 String 클래스 객체를 만들어내는 것임
- 일반적으로 기본형 비교는 == 연산자를 사용하지만
String 객체간의 비교는 .equals() 메소드를 사용하는 이유
- equals() 메서드 뿐만 아니라 다른 값 변경 메서드(concat 등)도 동일하게 새로운 인스턴스를 생성함
장점
- 불변 객체를 사용하면 해당 인스턴스에 대한 신뢰도가 생김
ㄴ 값이 변경되지 않기 때문에 믿고 사용 가능
- Thread-safe
ㄴ 동일한 값을 가지고 있어 동기화를 신경쓰지 않아도 됨
단점
- 불변 객체에 새로운 값이 필요할 때마다 새로운 객체 생성됨
ㄴ 리소스 소모
ㄴ 하지만, GC가 사용되지 않는 객체는 메모리에서 삭제하기 때문에 괜찮음