불변 객체(Immutable Object)란?
객체 지향 프로그래밍에 있어서 불변객체는 생성 후 그 상태를 바꿀 수 없는 객체를 말한다. 그렇기에 생성이후 신뢰할 수 있는 객체가 된다.
대표적인 불변객체로는 Boolean, Integer, String, BigDecimal..
이 존재한다.
불변이라는 키워드를 놓고 보면 final이라는 예악어가 생각날 수 있는데, 단순히 final을 사용한다고 해서 불변객체를 만드는 것이 아니다. final 예약어를 사용시 각 효과는 아래와 같다.
- 변수 : 값을 수정할 수 없는 상수로 만들어줌
- 메소드 : Overriding을 할 수 없음
- 클래스 : 상속이 불가능 함
즉, 객체 선언시 final을 사용한다고 하더라도 객체 내부상태는 변경할 수 있다.
불변 객체 장점
- Thread-Safe하여 병렬 프로그래밍에 유용하고 동기화를 고려하지 않아도 된다.
- 불변객체는 항상 동일한 값을 보장하므로 동기화를 신경쓸 필요가 없다.
- 내부 상태의 변경이 없기 때문에 Cache, Map, Set등의 요소로 활용하기에 적합하다.
- 외부에 객체에 대해 변경할 수 없기 때문에 안정성이 있다.
- 가비지 컬렉션의 성능을 높일 수 있다.
- 상태의 변경이 필요한 경우 새로운 객체를 생성해서 사용해야 한다. 이때 새로운 객체가 많이 생성될 경우 성능상 좋지 않을거라 생각할 수 있다. 그러나 오라클에서는 객체 생성에 대한 비용은 과대평가되고있으며, 이는 불변객체를 이용한 효율로 충분히 상쇄할 수 있다고 말했다. 불변객체 이용시 내부의 객체에 대해서는 GC 스캔에서 제외된다. 즉, CG의 스캔빈도와 범위가 줄게되어 GC의 성능에 도움이 된다.
불변 객체 생성 규칙
불변객체를 생성시에는 아래와 같은 규칙이 존재한다.
- setter 메서드를 제공하지 않는다.(read-only method만 제공. 다만, 타입에 따라 방어적 복사를 통해 참조를 끊어 제공하기도 한다.
- 모든 필드를 private, final로 선언한다.
- 하위 클래스에서 overriding을 방지하기 위해 클래스를 final로 선언한다.
- 객체를 생성하기 위한 생성자 또는 정적 팩토리 메서드를 추가한다.
- 인스턴스 필드에 가변객체가 포함된다면 방어적 복사를 이용하여 전달한다.
직접 만들어보자
원시 타입만 있는 경우
public class BaseObject {
private int value;
public BaseObject(final int value) {
this.value = value;
}
public void setValue(int newValue) {
this.value = newValue;
}
// getter는 생략
}
위의 코드의 경우 외부에서 Setter를 통해 value의 값을 변경할 수 있기 때문에 불변객체라 할 수 없다. 필드가 원시타입만 존재하므로 final
키워드를 통해 손쉽게 불변객체로 만들 수 있다.
public class BaseObject {
private final int value;
public BaseObject(final int value) {
this.value = value;
}
// getter는 생략
}
필드에 final을 사용하여 상수로 만들었으므로 당연히 Setter를 사용하는 것은 불가능하고 해당 객체의 value를 변경하기 위해선 재할당하는 수 밖에 없다.
참조 타입이 있는 경우
참조 타입의 경우에는 final 사용, setter 미작성으로만은 불변객체를 만들 수 없다.
public class Animal {
private final Age age;
public Animal(final Age age) {
this.age = age;
}
public Age getAge() {
return age;
}
}
class Age {
private int value;
public Age(final int value) {
this.value = value;
}
public void setValue(final int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
Animal 클래스에서 final을 사용했고 Setter를 구현하지 않았지만 불변객체가 될 수 없다. Animal 클래스의 Age의 값을 아래의 코드로 변경할 수 있기 때문이다.
public static void main(String[] args) {
Age age = new Age(1);
Animal animal = new Animal(age);
System.out.println(animal.getAge().getValue());
// Output: 1
animal.getAge().setValue(10);
System.out.println(animal.getAge().getValue());
// Output: 10
}
즉, 불변 객체의 참조 변수 또한 불변이어야 한다.
필드에 참조 타입이 있는 경우 객체, Array, List 등을 참조할 수 있다. 이런 상황에 대해 불변객체를 만들어보자.
1) 참조 변수가 일반 객체인 경우
참조 변수인 Age도 final 키워드를 총해 불변 객체로 만들어 해결할 수 있다.
public class Animal {
private final Age age;
public Animal(final Age age) {
this.age = age;
}
// getter
}
class Age {
private final int value;
public Age(final int value) {
this.value = value;
}
// getter
}
2) Array일 경우
public class ArrayObject {
private final int[] array;
public ArrayObject(final int[] array) {
this.array = Arrays.copyOf(array,array.length);
}
public int[] getArray() {
return (array == null) ? null : array.clone();
}
}
배열인 경우, 생성자에서 배열을 받아 copy해서 저장하도록 하고 getter를 clone을 반환하면 된다. 배열을 그대로 참조하거나, 그대로 반환할 경우 외부에서 배열 내부값을 변경시킬 수 있기 때문에 clone 반환시 외부에서 값을 변경시킬 수 없다. 만일 원시 타입 배열이아닌 Animal[]과 같은 형태라면 해당 객체는 불변객체여야 한다.
3) List인 경우
Array와 마찬가지로 생성시 생성자 인자를 그대로 참조하지 않고, 새로운 List를 만들어 값을 복사하도록 해야 한다. 그리고 unmodifiableList 메서드를 사용하여 getter를 통한 값 추가/삭제가 불가능 하도록 작성한다,
import java.util.Collections;
import java.util.List;
public class ListObject {
private final List<Animal> animals;
public ListObject(final List<Animal> animals) {
this.animals = new ArrayList<>(animals);
}
public List<Animal> getAnimals() {
return Collections.unmodifiableList(animals);
}
}
https://yeoonjae.tistory.com/entry
https://dev-cool.tistory.com/23
https://velog.io/@conatuseus/Java-Immutable-Object불변객체
https://velog.io/@roro/Java-Object-클래스-clone
'Backend > JAVA' 카테고리의 다른 글
[JAVA] ArrayList와 LinkedList의 차이점은? (0) | 2024.02.05 |
---|---|
[JAVA] Java Generic의 공변, 반공변, 무공변 (2) | 2024.02.02 |
[JAVA] 스레드에서의 경쟁상태(Race condition) (1) | 2024.01.30 |
[JAVA] 쓰레드(Thread)란? Java의 동기화 기능은 무엇일까? (2) | 2024.01.25 |
[JAVA] iterator와 iterable의 차이는 무엇일까? (0) | 2024.01.24 |