subList(from, to) 는 새 리스트를 만들지 않는다. 원본을 가리키는 창을 돌려준다.
세 가지 결과
List<String> all = new ArrayList<>(List.of("a","b","c","d","e"));
List<String> part = all.subList(1, 3); // [b, c]
part.set(0, "B"); // ① 뷰를 고치면 원본이 바뀐다 → all = [a, B, c, d, e]
all.add("f"); // ② 원본을 구조적으로 바꾸면
part.get(0); // 뷰는 무효가 된다 → ConcurrentModificationException
③ 원본이 GC 되지 않는다. 뷰가 원본 리스트를 참조하고 있어, 원소 두 개짜리 조각을 들고 있다고 생각해도 뒤에 백만 개짜리 배열이 통째로 살아남는다.
왜 이렇게 설계했나
복사를 안 하기 때문에 O(1) 이다. list.subList(a,b).clear() 로 구간을 통째로 지우는
관용구도 이 성질 덕분에 성립한다. 비용을 안 치르는 대신 원본과 운명을 공유한다.
안전하게 쓰는 법
List<String> copy = new ArrayList<>(all.subList(1, 3)); // 독립 스냅숏
메서드 밖으로 넘길 값이라면 반드시 복사한다. 뷰를 반환하는 API는 "내 내부를 만질 수 있는 창"을 넘기는 것과 같다.
같은 성질을 가진 것들
Collections.unmodifiableList(읽기 전용 뷰), Map.keySet()·values()·entrySet() 도 모두 뷰다.
keySet().remove(k) 가 맵에서 실제로 지워지는 이유가 그것이다.