Java record를 쓰는데 내부 리스트가 외부에서 계속 바뀐 이유
문제 발생
record로 만든 값 객체가 "불변"이라고 생각하고 여기저기 공유해서 썼는데, 한 곳에서 내부 리스트를 수정하니 다른 곳에서도 그 변경이 그대로 보였습니다.
public record Team(String name, List<String> members) {}
List<String> members = new ArrayList<>(List.of("a", "b"));
Team team = new Team("dev", members);
members.add("c"); // team.members()도 함께 바뀜
team.members().add("d"); // 이것도 가능 — record가 진짜 불변이 아님원인 분석
record가 보장하는 것은 필드 자체의 재할당 불가입니다 — team.members = otherList 같은 재할당은 컴파일 에러입니다. 하지만 필드가 가리키는 객체(List<String>)가 원래 가변 객체라면, 그 객체의 내용을 바꾸는 것까지 막아주지는 않습니다.
위 예시에서 Team의 members 필드는 생성자에 넘긴 것과 같은 ArrayList 참조를 그대로 저장합니다. 그래서 외부에서 그 원본 리스트를 수정하면 Team 내부에서 보이는 값도 함께 바뀝니다. 이런 걸 "얕은 불변성(shallow immutability)"이라고 부릅니다 — record의 필드 슬롯 자체는 불변이지만, 그 안에 담긴 가변 객체까지 불변으로 만들어주지는 않습니다.
해결 방안
- 컴팩트 생성자(compact constructor)에서 방어적 복사를 합니다 — 전달받은 컬렉션을 그대로 저장하지 않고, 수정 불가능한 새 컬렉션으로 감싸서 저장합니다.
public record Team(String name, List<String> members) {
public Team {
members = List.copyOf(members); // 불변 복사본으로 교체
}
}List.copyOf()는 수정 시도 시 UnsupportedOperationException을 던지는 진짜 불변 리스트를 반환합니다.
- 접근자(getter)에서도 원본을 그대로 노출하지 않고 불변 뷰를 반환하는 방식으로 방어할 수 있지만, 컴팩트 생성자에서 애초에 불변 컬렉션으로 바꿔두는 편이 더 근본적입니다.
record의 필드가 다른 가변 클래스(날짜, 커스텀 객체 등)라면 그 타입도 마찬가지로 방어적 복사나 불변 타입 사용을 고려해야 합니다.- 애초에 값 객체의 필드는 가능하면 불변 타입(
String, 원시 타입, 다른 불변record,List.of()로 만든 불변 컬렉션 등)으로만 구성하는 것이 이런 문제 자체를 없애는 가장 확실한 방법입니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.