일급 콜렉션
일급 콜렉션은 콜렉션 외의 필드가 존재하지 않는 클래스를 의미한다.
왜 사용하는가
콜렉션을 필드로 사용하는 도메인 객체는 데이터 일관성에 관한 문제가 생길 가능성이 존재한다.
다음의 예시를 살펴보자.
@AllArgsConstructor
public class Order {
private final long orderId;
private final String name;
private final List<LocalDateTime> orderTimes;
}
public void order() {
List<LocalDateTime> orderTimes = new ArrayList<>();
orderTimes.add(LocalDateTime.now());
orderTimes.add(LocalDateTime.now());
Order order = new Order(0, "NAME", orderTimes);
orderTimes.add(LocalDateTime.now());
// order.orderTimes의 크기는 2? 3?
}
비록 Order
의 모든 필드가 final으로 불변임을 보장하지만 orderTimes에 대해서는 약간 다르다.
final은 그저 무조건 한 번 할당하게 한다는 의미로, List<LocalDateTime>
인스턴스의 주소값을 가질 뿐이기 때문이다.
따라서 Order
인스턴스 생성 뒤 orderTimes에 데이터를 더 넣어주면 바로 반영되는 것이다.
데이터 불일치 해결하기
일급 콜렉션을 사용하여 데이터의 정합성을 보장할 수 있도록 변경해보자.
public class OrderTimes {
private final List<LocalDateTime> times;
public OrderTimes(List<LocalDateTime> times) {
this.times = new ArrayList<>(times);
}
public List<LocalDateTime> getTimes() {
return Collections.unmodifiableList(times);
}
}
@AllArgsConstructor
public class Order {
private final long orderId;
private final String name;
private final OrderTimes orderTimes;
}
OrderTimes
라는 클래스를 새로 만들었다.
그리고 생성 시점에서 새로운 ArrayList
를 할당하여 필드로 사용하는 것을 강제했다.
getTimes()
메소드에서는 변경 불가능한 리스트로 반환한다.
이렇게 되면 사용자는 OrderTimes
를 생성한 뒤 데이터를 직접 통제할 수 없다.
주의점
여기서도 주의할 점이 하나 있는데, 생성자를 다시 한번 살펴보자.
// 원래 코드
public OrderTimes(List<LocalDateTime> times) {
this.times = new ArrayList<>(times);
}
// 만약 이렇다면?
public OrderTimes(List<LocalDateTime> times) {
this.times = times;
}
매개변수가 List
타입으로 들어옴에도 불구하고 this.times에 새로운 ArrayList
를 할당한 뒤 기존 데이터를 복사하여 넘겨주었다.
복사하여 새로 할당하지 않으면 List<LocalDateTime>
의 인스턴스로 OrderTimes
를 통제할 수 있게 되니 꼭 새로운 인스턴스로 복사하여 넘겨주는 것이 안전하다.
Collections.unmodifiableList
이 메소드는 자바 내장 라이브러리에서 제공하고 있는 메소드로,
이 메소드를 통해 반환된 콜렉션은 데이터를 변경할 수 없다는 특징을 갖고 있다.
즉 원본을 가지지만 변경할 수 없도록 통제하는 프록시인 것이다.
라이브러리 코드를 파헤쳐서 들어가보자.
static class UnmodifiableList<E> extends Collections.UnmodifiableCollection<E>
implements List<E> {
private static final long serialVersionUID = -283967356065247728L;
final List<? extends E> list;
UnmodifiableList(List<? extends E> list) {
super(list);
this.list = list;
}
...
public E get(int index) {
return this.list.get(index);
}
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
}
생성자를 보면 원본을 그대로 가져와 필드에 할당한다.
get 메소드는 원본 콜렉션과 기능이 같다.
데이터를 변환하는 set, add remove는 예외를 발생시켜 변경할 수 없도록 강제한다.
만약 getTimes()
호출 후 데이터를 적절하게 변경해야 하지만 원본은 유지하고 싶다면 복사해서 넘겨주는 편이 좋다.
public List<LocalDateTime> getTimes() {
return List.copyOf(times);
}
'Java' 카테고리의 다른 글
ATDD 아주 간단히 찍먹 (0) | 2021.07.21 |
---|---|
멱등성과 테스트 (0) | 2020.11.08 |
Java Stream (6) 스트림 생성 (0) | 2020.07.22 |
Java Stream (5) 기본 자료형 스트림 (0) | 2020.07.22 |
Java Stream (4) 스트림 주요 메소드 (0) | 2020.07.17 |