ONE QUESTION AT A TIME.

천천히 생각하고, 분명하게.

TODAY’S QUESTION760 / 1500
언어·런타임난이도 하5분 답변 목표 · 보강본

final List를 선언했는데도 원소가 바뀝니다. final 변수, 참조 대상 객체의 변경, 진짜 불변 객체를 어떻게 구분하나요?

잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?

답변과 점검 포인트 펼치기답변 접기
01

구두 답변

정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.

final은 변수가 보관한 값을 다시 대입하지 못하게 하는 한정자입니다. 변수 타입이 참조 타입이면 고정되는 것은 참조 자체이지, 그 참조가 가리키는 객체의 내부 상태가 아닙니다. 따라서 final List<String>에 다른 리스트를 대입할 수는 없지만, 리스트가 가변 구현체라면 add나 remove로 상태를 바꿀 수 있습니다. 배열도 객체이므로 final 배열의 요소는 변경할 수 있습니다.

참조의 고정과 상태의 고정

아래는 main 등 메서드 안에서 실행하는 설명용 코드 조각입니다.

final java.util.List<String> names = new java.util.ArrayList<>();
names.add("Java");                 // 가능
System.out.println(names);         // [Java]
// names = new java.util.ArrayList<>(); // 컴파일 오류

java.util.List<String> view = java.util.Collections.unmodifiableList(names);
names.add("JVM");                  // 원본 변경은 가능
System.out.println(view);           // [Java, JVM]

unmodifiableList는 해당 뷰를 통한 변경을 막지만 원본이 바뀌면 뷰에서도 변화가 보일 수 있습니다. 입력 시점의 복사본을 독립적으로 보관하려면 List.copyOf(names)로 원본 리스트의 이후 구조 변경이 반영되지 않는 수정 불가 리스트를 얻을 수 있습니다. 다만 API가 항상 새로운 리스트 객체를 할당한다고 보장하지는 않으므로 객체 동일성을 복사 보장으로 사용하지 않겠습니다. 이는 원본을 계속 바라보는 뷰와 다릅니다. 다만 리스트 원소 자체가 가변 객체라면 리스트 구조만 고정될 뿐 원소 내부의 변경까지 막지는 않습니다.

방어적 복사와 공유

객체를 불변으로 설계하려면 상태를 생성 시 확정하고 변경 메서드를 제공하지 않으며, 내부 가변 컬렉션을 외부에 그대로 반환하지 않아야 합니다. 생성자에서도 호출자가 가진 가변 객체를 그대로 저장하지 말고 필요한 경우 방어적 복사를 하겠습니다. final은 재할당 규칙이고 불변성은 객체 설계의 결과이므로, 둘을 같은 뜻으로 사용하면 공유 상태 변경을 놓치게 됩니다.

선택 기준과 검증

불변성을 점검할 때는 컬렉션의 원소도 따라가야 합니다. List<String>은 안전해도 원소가 배열이나 가변 DTO이면 getter를 통해 내부 상태가 바뀔 수 있습니다. 필요한 도메인 노드만 깊게 복사하고 나머지는 불변 타입으로 제한하겠습니다. 이 방식은 읽기 락을 줄이는 대신 갱신 때 새 객체와 복사 비용을 만들므로 변경 빈도에 맞춰 선택합니다.

읽기 전용 API와 불변 데이터도 구분해야 합니다. 호출자에게 add를 제공하지 않는 인터페이스라도 다른 소유자가 원본 리스트를 바꾸면 값이 달라집니다. 불변 스냅샷은 관찰 가능한 값이 이후 바뀌지 않아야 하므로 원본과의 별칭을 끊거나 원본도 불변이라는 전제가 필요합니다. List.copyOf는 null 원소를 거절한다는 API 조건도 있어 입력에서 null을 허용했던 코드의 대체제로 넣기 전에 검사해야 합니다.

final 필드의 초기화 안전성에는 생성 중 this가 외부로 새지 않는 등의 전제가 있습니다. 생성자에서 콜백을 등록해 아직 초기화되지 않은 객체를 다른 스레드가 보게 만들면 final만으로 안전하다고 단정할 수 없습니다. 이후 최신 스냅샷 참조를 공유하려면 volatile·락 등 적절한 공개 경로를 사용하고, 불변성이 메모리 가시성이나 여러 저장소의 원자적 커밋을 대신하지 않음을 구분하겠습니다.

02

득점 포인트

이해가 드러나는 지점을 확인해 보세요.

  • final 재대입과 참조 대상 변경을 구분한다.
  • 뷰·복사본·원소의 가변성을 실제 결과로 비교한다.
  • 방어적 복사와 불변 스냅샷의 비용을 판단한다.
03

감점 포인트

놓치기 쉬운 조건과 오해를 짚어 보세요.

  • final List의 원소와 원소 내부까지 모두 변경 불가라고 말한다.
  • unmodifiableList가 원본과 원소를 깊게 복사한다고 설명한다.
  • final 키워드만으로 공유 가변 그래프가 불변이 된다고 가정한다.
04

더 파고들 거리

하나 더 질문하며 이해의 범위를 넓혀 보세요.

득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.

원리부터 공부하고 싶다면

답변을 보충하는 개념·예제·슈도코드를 읽어 보세요.

← 전체 질문 보기