[Modern Java in Action] Chapter8. 컬렉션 API 개선

June 29, 2022 - 5 minute read -
book moder java in action

모던 자바 인 액션 8장에서는 개선된 컬렉션에 대해 소개한다.
개선된 사항, 컬렉션 팩토리, 새로운 기능들에 대해 자세히 살펴본다.


8.1 컬렉션 팩토리

자바 9에서는 작은 컬렉션 객체를 쉽게 만들 수 있는 방법들을 제공한다. 이와 같은 기능은 다음과 같이 코드를 간단하게 줄일 수 도 있고,
고정 크기의 리스트를 만들기 때문에 의도치 않게 컬렉션이 변화되지 않도록 막을 수 있다. (UnsupportedOperationException 발생)

List<String> friends = new ArraysList<>();
friends.add("Raphael");
friends.add("Olivia");
friends.add("Thibaut");

List<String> friends = Arrays.asList("Raphael", "Olivia", "Thibaut");
  • 리스트 팩토리 (List.of)
    • 변경할 수 없는 리스트 (추가하려고 하면 UnsupportedOperationException 발생)
    • null 요소 금지
      List<String> friends = List.of("Raphael", "Olivia", "Thibaut");
      
  • 집합 팩토리 (Set.of)
    • 변경할 수 없는 집합
    • 중복된 요소를 제공 불가 (IllegalArgumentException 발생)
      Set<String> friends = Set.of("Raphael", "Olivia", "Thibaut");
      
  • 맵 팩토리 (Map.of, Map.ofEntries)
    • 변경할 수 없는 맵
    • 키와 값이 열개 이하인 경우 Map.of, 이상인 경우 Map.ofEntries 사용
      Map<String, Integer> ageOfFriends = Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26);
      Map<String, Integer> ageOfFriends = Map.ofEntries(entry("Raphael", 30), 
                                                        entry("Olivia", 25), 
                                                        entry("Thibaut", 26));
      


8.2 리스트와 집합 처리

  • removeIf
    • 프레디케이트를 만족하는 요소를 제거
    • 기존 컬렉션을 변경
    • 코드가 단순해지고 ConcurrentMOdificationException 버그 방지
  • replaceAll
    • List에서 이용할 수 있으며 UnaryOperator 함수로 각 요소를 변경
    • 새로운 컬렉션을 생성하지 않고 간단하게 변경 가능


8.3 맵 처리

  • forEach 메서드
    • BiConsumer를 인수로 받는 키와 값을 반복하면서 확인하는 작업
    • 기존에는 Map.Entry<K, V> 을 이용함
      ageOfFriends.forEach((friend, age) -> System.out.println(friend + " is " + age + " yeas old"));
      
  • 정렬 메서드 (Entry.comparingByValue, Entry.comparingByKey)
    • 맵의 항목을 값 또는 키를 기준으로 정렬
      favoriteMovies.entrySet()
        .stream()
        .sorted(Entry.comparingByKey())
        .forEachOrdered(System.out::println);
      // Cristina = Matrix 
      // Olivia = James Bond 
      // Raphael = Star Wars 
      
  • getOrDefault 메서드
    • 첫 번째 인수는 키, 두 번째 인수는 기본값을 받고 키가 존재하지 않으면 기본값 반환
    • 기존에는 null 이 반환되어 NullPointerException 발생될 여지가 있었음
      // matrix 출력
      System.out.println(favoriteMovies.getOrDefault("not exists key", "matrix")) 
      
  • 계산 패턴(computeIfAbsent, computeIfPresent, comput)
    • computeIfAbsent: 제공된 키에 해당하는 값이 없으면 키를 이용해 새 값을 계산하고 추가
    • computeIfPresent: 제공된 키가 존재하면 새 값을 계산하고 맵에 추가
    • comput: 제공된 키로 새값을 계산하고 맵에 저장
  • 삭제 패턴 (remove)
    • 기존에는 remove는 삭제만 되었지만 자바 8에서는 특정한 키 값과 연관된 경우에만 제거하는 메서드가 제공된다.
      // 키와 값이 동일한 경우에만 삭제
      favouriteMovies.remove(key, value); 
      
  • 교체 패턴 (replaceAll, Replace)
    • replaceAll: 모든 항목의 값을 BiFunction 을 적용한 결과들로 교체
    • Replace: 키가 존재하면 맵의 값을 변경 (키가 특정 값으로 매핑된 경우에도 교체 가능)
  • 합침
    • merge 를 이용하면 값을 유연하게 합칠 수 있음
    • putAll 을 이용하면 합쳐지긴 하지만 중복된 키에 대한 처리 불가능
      // 두번째 인수는 키가 없거나 반환 값이 null 인 경우 기존 값으로 사용
      // 세번째 인수는 중복된 키가 어떻게 합쳐질 지 결정하는 BIFunction
      moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L) 
      

8.4 개선된 ConcurrentHashMap

ConcurrentHashMap 은 자료구조의 특정 부분만 잠궈서 동시 추가, 갱신 작업을 허용하여, Hashtable 버전에 비해 읽기 쓰기 연산 성능이 뛰어나다.

  • 리듀스와 검색
    • 키와 값으로 연산
      • forEach: 각 키와 값에 주어진 액션을 실행
      • reduce: 모든 키와 값을 제공된 리듀스 함수를 이용해 합침
      • search: 널이 아닌 값을 반환할 때까지 각 키와 값에 함수 적용
    • 키로 연산(forEachKey, reduceKey, searchKey)
    • 값으로 연산(forEachValue, reduceValues, searchValues)
    • Map.Entry 객체로 연산(forEachEntry, reduceEntries, searchEntries)
    • 함수를 실행할 때는 병렬성 기준값(threshold) 지정 필요
      • 맵의 크기가 기준 값보다 작으면 순차적으로 연산 실행
      • 기준값을 1로 지정하면 공통 스레드 풀을 이용해 병렬성 극대화
  • 계수(mappingCount)
    • int 범위를 넘어설 수 있어서 size 메서드 대신 mappingCount 메서드 사용
  • 집합뷰(newkeySet)
    • keySet 으로 생성한 집합은 맵과 서로 변경에 대해 영향을 받음
    • 서로 영향 없이 유지할 수 있도록 newKeySet 메서드 사용