자바/모던 자바 인 액션

[모던 자바 인 액션] 8장. 컬렉션 API 개선

Rudtjs 2022. 11. 10. 08:27

리스트와 집합에서 요소를 삭제하거나 바꾸는 관용 패턴을 적용시켜보자.
 

8.1 컬렉션 팩토리

자바에서 배열을 만들 때는 

List<String> array = new ArrayList<>();
array.add("1");
array.add("2");

이런 식으로 많이 사용할 것이다.
 
여기서 코드를 더 줄이기 위해 Arrays.asList()로 고정 크기 리스트를 만들어보자.

List<String> array 
	= Arrays.asList("1", "2");

 
고정크기라서 새 요소를 추가하거나 요소를 삭제를 할 수는 없다.
만약 여기서 리스트의 요소를 갱신하거나 삭제를 한다면 UnsupportedOperationException이 발생한다.
 

UnsupportedOperationException 예외 발생

내부적으로 고정된 크기로 배열을 만들었을 때 이와 같은 일이 발생한다.
집합도 마찬가지로 Arrays.asSet()을 이용하면 같은 오류가 발생할 수 있다. 이 두 방법은 매끄럽지 못하고 불필요한 객체 할당을 필요로 한다. 
 

8.1.1 리스트 만들기

자바 9에서 지원해주는 List.of , Set.of 팩토리 메서드를 이용해서 리스트를 만들 경우 고정된 크기여서 수정하려고 하면 오류가 발생한다. 
맵 팩토리는 키와 값이 둘 다 들어 있어야 하는 컬렉션으로 단순 리스트나 집합보다는 복잡하다. 
 

8.2 리스트와 집합 처리

자바 8부터는 List, Set 인터페이스에 removeIf. replaceAll. sort를 추가하여 리스트를 바꿀 수 있는 기능들을 추가하였다.

  • removeIf: 프레디케이트(조건)를 만족하는 요소를 제거한다. List나 Set을 구현하거나 그 구현을 상속받은 모든 클래스에 이용할 수 있다.
  • replaceAll: 리스트에서 이용할 수 있는 기능으로 요소를 바꿀 수 있다.
  • sort: List 인터페이스에서 제공하는 기능으로 리스트를 정렬한다.  

 

8.3 맵 처리

맵에서 키와 값을 반복하면서 요소를 만드는 작업은 귀찮을 것이다. 그런 불편함을 덜어주고자 forEach 메서드를 지원하여 코드를 간결하게 만들어준다.

ageFriends.forEach((friend, age) -> System.out.println(friend + "is" + age + "years old"));

 

8.3.1 정렬 메서드

두 개의  새로운 유틸리티를 이용하여 맵의 항목을 값 또는 키를 기준으로 정렬할 수 있다.

  • Entry.comparingByValue
  • Entry.comparingByKey
favorites
	.entrySet()
	.stream()
	.sorted(Entry.comparingByKey) //key값으로 정렬시킬 수 있다.
	.forEachOrdered(System.out.println);

이렇게 정렬 요청을 할 때 키가 맵에 존재하지 않을 때는 getOrdefault 메서드를 이용하여 처리할 수 있다.
 

8.3.2 getOrdefault 메서드

키가 존재하지 않으면 널이 반환되어 NPE 오류가 발생한다. 이 문제는 기본값을 반환하는 방식으로 해결할 수 있다.
getOrDefault 메서드는 첫 번째 인수로 키를, 두 번째 인수로 기본값을 받으며 맵에 키가 존재하지 않으면 두 번째 인수로 받은 기본값을 반환한다.
 

8.3.3 패턴 정리

맵에 키가 존재하는지 여부에 따라 동작을 실행하고 결과를 저장해야 하는 상황이 필요할 때

  • computeIfAbsent: 제공된 키에 해당하는 값이 없으면, 키를 이용해 새 값을 계산하고 맵에 추가한다.
  • computeIfPresent: 제공된 키가 존재하면 새 값을 계산하고 맵에 추가한다.
  • compute: 제공된 키로 새 값을 계산하고 맵에 저장한다.
if (movie == null) {
	movie = new ArrayList<>();
	toMovie.put(friend, movie);
}
movie.add("123");

기존에 하던 null체크 방식을 computeIfAbsent를 이용하여 코드를 줄일 수 있다.

toMovie.computeIfAbsent("111", name -> new ArrayList<>())
		.add("123");

computeIfAbsent 메서드는 현재 키와 관련된 값이 맵에 존재하며 널이 아닐 때만 새 값을 계산한다. 
 

  • 항목을 제거하고 싶다면 remove(key, value)를 이용하여 삭제할 수 있다.
  • 항목을 교체하고 싶다면 replaceAll, replace를  이용하여 교체할 수 있다.

 

8.4 개선된 ConcurrentHashMap 

ConcurrentHashMap 클래스는 내부 자료구조의 특정 부분을 잠글 수 있고, 갱신을 허용한다. 기존 버전보다 읽기 쓰기 연산 성능이 월등하다.
 

8.4.1 리듀스와 검색

ConcurrentHashMap은 세 가지 새로운 연산을 제공해준다.

  • forEach: 각 (키, 값) 쌍에 주어진 액션을 실행
  • reduce: 모든 (키, 값) 쌍을 제공된 리듀스 함수를 이용해 결과로 합침
  • search: null이 아닌 값을 반환할 때까지 각 (키, 값) 쌍에 함수를 적용
  • 키로 연산 하고 싶다면 뒤에 Key, 값으로 연산하고 싶다면 뒤에 Value, 객체로 연산하고 싶다면 뒤에 Entry를 붙여주면 된다.