모던 자바 인 액션 | Part III 스트림과 람다를 이용한 프로그래밍

모던 자바 인 액션

컬렉션 API, 리팩터링, 테스팅, 디버깅, 람다를 이용한 도메인 전용 언어에 대해 학습합니다.



Chapter8 - 컬렉션 API 개선

Java 8과 그 이후 버전에서 도입된 컬렉션 API의 주요 개선 사항과 새로운 기능을 정리한다.

컬렉션 팩토리

Java 9에서 도입된 컬렉션 팩토리 메서드는 불변 컬렉션을 쉽게 생성할 수 있다.

  • 리스트 팩토리 : List.of()
  • 집합 팩토리 : Set.of()
  • 맵 팩토리 : Map.of()

기존 방식보다 간결하며, 생성된 컬렉션은 변경 불가(immutable)하다.

1
2
3
4
5

List<String> list = List.of("A", "B", "C");
Set<String> set = Set.of("X", "Y", "Z");
Map<String, String> map = Map.of("key1", "value1", "key2", "value2");

특징

  • 컬렉션은 불변(Immutable)하여 요소를 추가하거나 삭제가 불가능
  • null값을 허용하지 않음

리스트와 집합 처리

  • removeIf : Predicate를 만족하는 요소를 제거
  • replaceAll : UnaryOperator 함수를 이용하여 요소를 바꿈
    • UnaryOperator : 입력과 출력이 같은 타입인 연산
  • sort : List 인터페이스에서 제공하는 기능으로 리스트를 정렬

맵처리

  • forEach : Map에서 키와 값을 반복하며, BiConsumer를 인수로 받는 메서드를 지원한다.
    • BiConsumer : 두 개의 입력값(매개변수)을 받아 연산을 수행하지만 결과를 반환하지 않는 연산
  • 정렬 메서드 : 아래 두개의 유틸리티 메서드를 이용하여, 값 또는 키를 기준으로 정렬할 수 있다.
    • Entry.comparingByValue
    • Entry.comparingByKey
  • getOrDefault : 첫 번째 인수로 키를, 두 번째 인수로 기본값을 받아 맵에 키값이 존재하지 않으면 두 번째 인수인 기본값을 반환한다.

계산 패턴

Map의 키와 값을 기반으로 새 값을 동적으로 생성하거나 변경한다.

  • computeIfAbsent : 키가 없으면 새 값을 계산하여 추가
  • computeIfPresent : 키가 존재할 경우, 새 값을 계산하여 교체
  • compute : 키가 존재하거나 없거나 상관없이 값을 계산(값이 null이면 삭제)
1
2
3
map.computeIfAbsent("A", k -> "Value for A");
map.computeIfPresent("A", (k, v) -> v + " Updated");
map.compute("B", (k, v) -> (v == null) ? "Default Value" : v + " Updated");

교체 패턴

맵에 이미 존재하는 값을 조건에 따라 변경하거나 삭제한다.

  • replace : 특정 키의 값이 존재하면 새 값으로 교체
  • replaceAll : 모든 키-값 쌍에 대해 함수를 적용해 값을 교체
1
2
map.replace("A", "New Value");
map.replaceAll((k, v) -> v + " Replaced");

합침

맵의 기존 값과 새 값을 조건에 따라 병합한다.

  • merge : 키가 존재하면 기존 값과 새 값을 병합, 키가 없으면 새 값을 삽입
1
map.merge("A", "New Value", (oldVal, newVal) -> oldVal + ", " + newVal);

개선된 ConcurrentHashMap

Java에서 멀티스레드 환경에서 안전하게 사용될 수 있는 해시맵 구현체로, 성능 최적화와 스레드 안전성을 동시에 제공하며, HashMap과 Hashtable의 단점을 보완한 컬렉션이다.

스레드 안전성

  • 내부적으로 분할 잠금(Lock Striping) 기술을 사용하여 성능과 동시성을 개선
  • 맵 전체가 아닌 특정 버킷(Bucket)에만 락을 걸기 때문에 다중 스레드에서도 효율적으로 동작

성능 최적화

  • 락 경쟁 감소 : 데이터 구조를 여러 세그먼트(Segment)로 나누어 부분적인 락만 사용
  • 비교적 낮은 락 비용 : Java 8부터는 세그먼트 기반 락을 제거하고 버킷에 대한 락만 사용
  • 읽기 작업 대부분이 락 없이 수행 가능