Immutable List는 수정(추가, 삭제, 변경)이 불가능한 List 다. 컬렉션이 생성된 후 변경되는 것을 방지할 때 사용할 수 있다.
Immutable List 가 아닌 경우와 Immutable List를 만드는 방법을 알아보자.
Immutable List가 아닌 경우
final 키워드
variable may only be assigned to once.
final은 불변을 위한 키워드가 아닌 재할당을 금지하는 키워드다. final로 선언된 list는 add, remove, set과 같은 함수를 통해 list의 안 element를 변경할 수 있다. 단지 final로 선언된 list는 항상 같은 list를 참조하고 있을 뿐이다.
아래 코드를 통해 값이 추가되는 경우와 재할당이 되지 않는 경우를 확인해보자.
- 값이 추가되는 경우
results는 리스트의 크기가 4 였으나 2번의 add함수를 이용해 값을 추가해 사이즈가 6이 되었다. 즉, final로 선언된 List 요소를 변경할 수 있다.@Test @DisplayName("final 리스트 값 추가 테스트") void final_list_add(){ final List<Integer> results = getResult(); results.add(5); results.add(6); assertThat(results).hasSize(6); } private List<Integer> getResult() { return Stream.of(1,2,3,4) .collect(Collectors.toList()); }
- 재할당은 되지 않음
@Test @DisplayName("final 리스트 재할당 테스트") void final_list_reassignment(){ final List<Integer> results = getResult(); results = getResult(); }
final 변수는 재할당을 할 수 없기 때문에 아래와 같은 컴파일 에러가 발생한다.
Arrays.asList()
Array.asList()는 고정된 사이즈의 List를 반환한다. add 나 remove 함수를 호출하면 아래 테스트와 같이 UnsupportedOperationException 이 발생한다.
@Test
@DisplayName("Arrays.asList add 테스트")
void arrays_as_list_add() {
List<String> students = Arrays.asList("Larry", "Moe", "Curly");
assertThrows(UnsupportedOperationException.class, () -> students.add("Tom"));
}
값을 추가하거나 삭제할 수는 없다.
하지만 값을 변경할 수는 있다. 아래 테스트를 통해 확인해보겠다.
@Test
@DisplayName("Arrays.asList 값 변경 테스트")
void arrays_as_list_set() {
List<String> students = Arrays.asList("Larry", "Moe", "Curly");
students.set(0,"Tom");
assertThat(students).hasSize(3)
.doesNotContain("Larry")
.contains("Tom");
}
set를 통해 첫번째 인덱스에 있는 값을 Larry에서 Tom으로 변경하였다.
Array.asList()로 생성된 List는 값을 추가하거나 삭제할 수는 없으나, 변경할 수는 있다.
Collections.unmodifiableList()
Collections.unmodifiableList()는 읽기 전용 리스트를 반환한다. 아래 테스트와 같이 Collections.unmodifiableList()를 이용한 unmodifiableResults에 add함수를 통해 값을 추가하려고 하면 UnsupportedOperationException 발생한다.
@Test
@DisplayName("Collections.unmodifiableList add 테스트")
void collections_unmodifiable_add() {
List<String> results = new ArrayList<>();
results.add("a");
results.add("b");
results.add("c");
results.add("d");
List<String> unmodifiableResults = Collections.unmodifiableList(results);
assertThrows(UnsupportedOperationException.class, () -> unmodifiableResults.add("Tom"));
}
하지만 Collections.unmodifiableList()가 참조하고 있는 results를 통해 unmodifiableResults를 변경할 수 있다.
아래 테스트 코드를 통해 결과를 확인해보자.
@Test
@DisplayName("Collections.unmodifiableList 변경 테스트")
void collections_unmodifiable_() {
List<String> results = new ArrayList<>();
results.add("a");
results.add("b");
results.add("c");
results.add("d");
List<String> unmodifiableResults = Collections.unmodifiableList(results);
results.add("e");
results.add("f");
results.add("g");
assertThat(unmodifiableResults).hasSize(7)
.contains("e", "f","g");
}
Collections.unmodifiableList()로 반환되는 List로는 직접적인 수정은 불가하지만, 참조하고 있는 List에 의한 수정은 막을 수 없다.
Immutable List 만들기
Java 9 이상
List.of()를 이용해 Immutable List를 만들 수 있다.
자바 9부터 List, Set, Map 인터페이스에 Immutable List, Set, Map을 만들기 위한 Static Factory Method가 추가되었다.
add, set, remove 같은 함수를 실행하면 UnsupportedOperationException이 발생한다.
@Test
@DisplayName("List.of add 테스트")
void list_of_add() {
List<String> results = List.of("a", "b", "c");
assertThrows(UnsupportedOperationException.class, () -> results.add("d"));
}
Java 8
자바 9이상이면 List.of()를 이용해 Immutable List를 만들면 된다. 하지만 자바 8에서는 List.of()가 없으므로 아래의 방법을 이용해 Immutable List를 만들 수 있다.
- Stream API 이용
@Test
@DisplayName("Jdk8 immutable List 생성 테스트")
void create_immutable_list() {
List<String> unmodifiableList = Stream.of("1","2","3")
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
assertThrows(UnsupportedOperationException.class, () -> unmodifiableList.add("4"));
}
- Arrays 와 Collections API 이용
List<String> results = Arrays.asList("a", "b", "c");
results = Collections.unmodifiableList(results);
results.add("e") // UnsupportedOperationException
result.remove(0) // UnsupportedOperationException