5.3 KiB
5.3 KiB
collections / immutability 기준
목적
컬렉션은 기본적으로 immutable-first로 다룬다.
변경이 꼭 필요한 로컬 조립 단계에서만 mutable 컬렉션을 허용하고, 경계를 넘길 때는 수정 불가 snapshot 또는 명시적 불변 구조로 바꾼다.
공식 의미
- JDK의
List.of,Set.of,Map.of,copyOf,toUnmodifiable*는 수정 불가 컬렉션을 만든다. Collections.unmodifiableXXX는 원본 컬렉션을 감싼 view일 뿐이다. 원본이 바뀌면 view도 바뀐다.- 컬렉션이 수정 불가여도 원소가 mutable이면 내용이 바뀐 것처럼 보일 수 있다.
List.of/List.copyOf계열은 null을 허용하지 않는다.Set.of와Map.of계열은 null을 허용하지 않으며, 중복 원소/중복 키를 허용하지 않는다.Set.of,Map.of,Map.ofEntries,toUnmodifiableSet,toUnmodifiableMap는 iteration order가 JVM 실행마다 달라질 수 있다.
기본 규칙
1. 기본은 immutable-first
다음 경우에는 mutable보다 immutable/unmodifiable 결과를 기본값으로 사용한다.
- 상수 컬렉션
- 설정/정책/권한 목록
- 외부에 반환하는 결과
- 공유되는 데이터
- 생성 후 더 이상 바뀌지 않아야 하는 상태
2. mutable 컬렉션은 로컬 조립 단계에서만 허용
다음 경우에는 mutable 컬렉션을 허용한다.
- 여러 source를 모아 결과를 만드는 임시 버퍼
- 반복적으로 add/remove가 필요한 내부 알고리즘
- aggregate/entity 내부에서 실제 상태 변경이 본질인 경우
단, 경계를 넘기기 전에 immutable/unmodifiable로 바꾼다.
3. 경계를 넘길 때는 defensive copy를 우선
다음 상황에서는 원본 컬렉션을 그대로 넘기지 않는다.
- 생성자에 받아서 필드로 저장할 때
- getter/응답 DTO/결과 객체로 반환할 때
- 다른 레이어로 넘길 때
기본 선택:
- snapshot이 목적 ->
List.copyOf,Set.copyOf,Map.copyOf - 작은 상수 컬렉션 ->
List.of,Set.of,Map.of - stream 수집 결과를 수정 불가로 고정 ->
Collectors.toUnmodifiableList/Set/Map
4. Collections.unmodifiableXXX는 “view”가 필요할 때만
이 API는 기본 선택이 아니다.
허용되는 경우:
- 원본과 동기화되는 read-only view가 정말 필요할 때
- legacy API와의 호환 때문에 wrapper view가 필요한 경우
기본 금지 이유:
- 원본이 바뀌면 view도 바뀐다
- defensive copy나 true immutable 의도와 다르다
5. copyOf를 snapshot 기본값으로 사용
이미 가지고 있는 mutable collection을 안전하게 보관/반환해야 하면 copyOf를 우선 검토한다.
예:
- 생성자에서 받은 list를 필드에 저장
- service 결과를 외부에 반환
- mapper 결과를 response/domain에 전달
6. 컬렉션 null 금지, empty 우선
컬렉션 필드/반환값/파라미터는 가능하면 null을 금지한다.
기본값:
- 없음 -> empty list / set / map
- null collection 금지
7. 원소의 immutability를 따로 본다
컬렉션만 수정 불가여도 원소가 mutable이면 완전한 불변이 아니다.
기본 원칙:
- 공유되는 컬렉션은 가능하면 immutable element를 담는다
- mutable element를 담는 경우 “shallow immutable only”라는 점을 의식한다
- 외부에서 element mutation이 가능한 구조를 장기 공유 상태로 두지 않는다
8. 결과 컬렉션의 의미를 명시적으로 선택
- 순서가 중요하면
List - 중복 제거가 목적이면
Set - key lookup이 목적이면
Map
불변이 목적이라면:
List.copyOfSet.copyOfMap.copyOfCollectors.toUnmodifiable*
를 우선 검토한다.
9. iteration order를 가정하지 않는다
Set.of, Map.of, Map.ofEntries, toUnmodifiableSet, toUnmodifiableMap는 iteration order가 랜덤화될 수 있다.
순서가 중요하면 List나 순서를 보장하는 별도 컬렉션 타입을 명시적으로 사용한다.
10. Collectors.toList() 결과를 mutable이라고 가정하지 않는다
수집 결과의 mutability가 중요하면 명시적으로 선택한다.
- 수정 불가 결과 필요 ->
stream.toList()또는Collectors.toUnmodifiableList() - 구체 mutable 타입 필요 ->
Collectors.toCollection(ArrayList::new)등
11. getter는 내부 mutable collection을 노출하지 않는다
다음 패턴을 금지한다.
- 내부
ArrayList참조를 그대로 반환 - 생성자에서 받은 collection 참조를 그대로 필드에 저장
- 외부에서 수정 가능한 map/set/list를 그대로 보관
12. domain/application에서 컬렉션은 가능한 한 non-null + stable
도메인/유스케이스 내부에서는:
- null collection 금지
- mutable shared state 최소화
- 변경 가능성이 없다면 불변 구조로 고정
프로젝트 기준 요약
- 기본은 immutable-first
- mutable은 로컬 조립 단계에서만 허용
- 경계에서는
copyOf/of/toUnmodifiable*를 기본값으로 사용 Collections.unmodifiableXXX는 view가 필요할 때만 제한적으로 사용- null collection 대신 empty collection
- 원소가 mutable이면 컬렉션만 막아도 완전한 불변이 아님
- iteration order가 중요한 곳에서
Set.of/Map.of계열 순서를 가정하지 않음