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

SunYerim·2024년 2월 18일
0

언어

목록 보기
8/11

8.1 컬렉션 팩토리

Arrays.asList() 팩토리 메서드를 이용하여 요소를 간단하게 추가할 수 있다.

List<String> friends = Arrays.asList("Raphael", "Olivia", "Thibaut");

고정 크기의 리스트를 만들었기 때문에 요소를 갱신할 수는 있지만 새로운 요소를 추가하거나 요소를 삭제할 순 없다. 새로운 요소를 추가하면 UnsupportedOperationException이 발생한다.

friends.set(0, "Richard"); // 갱신
friends.add("Thibaut"); // 추가 -> 에러

리스트를 인수로 받는 HashSet 생성자를 이용해보자.

Set<String> friends = new HashSet<>(Arrays.asList("Raphael", "Olivia", "Thibaut"));
// 스트림 사용
Set<String> friends
	= Stream.of("Raphael", "Olivia", "Thibaut")
					.collect(Collectors.toSet());

하지만 두 방법 모두 내부적으로 불필요한 객체 할당을 필요로 한다.

8.1.1 리스트 팩토리

List.of 팩토리 메서드를 이용해서 간단하게 리스트를 만든 후, 해당 리스트에 요소를 추가하려하면 Exception이 발생한다. 이는 변경할 수 없는 리스트가 만들어졌기 때문이다. 갱신도 마찬가지이다.

Collectors.toList() 컬렉터로 스트림을 리스트로 바꿀 수 있다.

8.1.2 집합 팩토리

집합은 오직 고유의 요소만 포함할 수 있다.

Set<String> friends = Set.of("Raphael", "Olivia", "Thibaut");

8.1.3 맵 팩토리

맵을 만드려면 키와 값이 있어야 한다. Map.of 팩토리 메서드에 키와 값을 번갈아 제공하는 방법으로 맵을 만들 수 있다.

Map<String, Integer> ageOfFriends
	= Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26);
	System.out.println(ageOfFriends);

열 개 이하의 키와 값 쌍을 가진 작은 맵을 만들 때는 해당 메서드가 유용하다. 그 이상의 맵에서는 Map.ofEntries 팩토리 메서드를 이용하는 것이 좋다.


8.2 리스트와 집합 처리

자바 8에서는 List, Set 인터페이스에 아래와 같은 메서드를 추가했다.

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

이들 메서드는 호출한 컬렉션 자체를 바꾼다.

컬렉션을 바꾸는 동작은 에러를 유발하며 복잡함을 더한다.

8.2.1 removeIf 메서드

아래는 숫자로 시작되는 참조 코드를 가진 트랜잭션을 삭제하는 코드이다.

for (Transaction transaction : transactions) {
	if (Character .isdigit(transaction.getReferenceCode().charAt(0))) {
		transactions.remove(transaction);
	}
}

이는 ConcurrentModificationException을 일으킨다.

내부적으로 for-each 루프는 Iterator 객체를 사용한다.

removeIf 메서드는 삭제할 요소를 가리키는 프레디케이트를 인수로 받는다.

transactions.removeIf(transaction ->
	Character.isDigit(transaction.getReferenceCode().charAt(0)));

요소를 바꿔야하는 상황에서는 replaceAll을 사용하자.

8.2.2 replaceAll 메서드

List 인터페이스의 replaceAll 메서드를 이용해 리스트의 각 요소를 새로운 요소로 바꿀 수 있다.

기존 컬렉션을 바꾸는 것은 ListIterator 객체를 이용하면 된다.

referenceCodes.replaceAll(code -> Character.toUpperCase(code.charAt(0)) + code.substring(1));

8.3 맵 처리

디폴트 메서드는 기본적인 구현을 인터페이스에 제공하는 기능 정도로 생각하면 된다.

8.3.1 forEach 메서드

자바 8버전부터 Map 인터페이스는 BiConsumer를 인수로 받는 forEach 메서드를 지원하므로 코드를 더 간단히 구현할 수 있다.

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

8.3.2 정렬 메서드

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

  • Entry.comparingByValue
  • Entry.comparingByKey

8.3.3 getOrDefault 메서드

기존에는 찾으려는 키가 존재하지 않으면 널이 반환되므로 NullPointerException을 방지하려면 요청 결과가 널인지 확인해야 한다. 기본값을 반환하는 방식으로 이를 해결할 수 있는데 getOrDefault 메서드를 이용하면 해결할 수 있다.

이 메서드는 첫 번째 인수로 키를, 두 번째 인수로 기본값을 받으며 맵에 키가 존재하지 않으면 두 번째 인수로 받은 기본값을 반환한다.

키가 존재하더라도 값이 널인 상황에서는 getOrDefault가 널을 반환할 수 있다

8.3.4 계산 패턴

  • computeIfAbsent: 제공된 키에 해당하는 값이 없으면, 키를 이용해 새 값을 계산하고 맵에 추가한다.
  • computeIfPresent: 제공된 키가 존재하면 새 값을 계산하고 맵에 추가한다.
  • compute: 제공된 키로 새 값을 계산하고 맵에 저장한다.

정보를 캐시할때 computeIfPresent를 활용할 수 있다.

8.3.5 삭제 패턴

제공된 키에 해당하는 맵 항목을 제거하는 remove메서드

자바 8에서는 키가 특정 값과 연관되었을 때만 항복을 제거하는 오버로드 버전 메서드를 제공한다.

favouriteMovies.remove(key, value);

8.3.6 교체 패턴

맵의 항목을 바꾸는 데 사용할 수 있는 두 개의 메서드가 맵에 추가되었다.

  • replaceAll: BiFunction을 적용한 결과로 각 항목의 값을 교체한다. 이 메서드는 이전에 살펴본 List의 replaceAll과 비슷한 동작을 수행한다.
  • Replace: 키가 존재하면 맵의 키를 바꾼다. 키가 특정 값으로 매핑되었을 때만 값을 교체하는 오버로드 버전도 있다.

8.3.7 합침

두 개의 맵을 합칠 때 putAll을 사용할 수 있다.

중복된 키가 없다면 잘 동작한다. 값을 좀 더 유연하게 합쳐야 한다면 새로운 merge 메서드를 이용할 수 있으며, 이 메서드는 중복된 키를 어떻게 합칠지 결정하는 BiFunction을 인수로 받는다.


8.4 개선된 ConcurrentHashMap

ConcurrentHashMap 클래스는 동시성 친화적이며 최신 기술을 반영한 HashMap 버전이다.

해당 클래스는 내부 자료구조의 특정 부분만 잠궈 동시 추가, 갱신 작업을 허용한다.

8.4.1 리듀스와 검색

  • forEach: 각 (키, 값) 쌍에 주어진 액션을 수행
  • reduce: 모든 (키, 값) 쌍을 제공된 리듀스 함수를 이용해 결과로 합침
  • search: 널이 아닌 값을 반환할 때까지 각 (키, 값) 쌍에 함수를 적용

네 가지 연산 형태를 제공한다.

  • 키, 값으로 연산
  • 키로 연산
  • 값으로 연산
  • Map.Entry 객체로 연산

이들 연산은 ConcurrentHashMap의 상태를 잠그지 않고 연산을 수행한다. 따라서 이들 연산에 제공한 함수는 계산이 진행되는 동안 바뀔 수 있는 객체, 값, 순서 등에 의존하지 않아야 한다.

이들 연산에 병렬성 기준값을 지정해야 한다.

8.4.2 계수

ConcurrentHashMap 클래스는 맵의 매핑 개수를 반환하는 mappingCount 메서드를 제공한다.

이 메서드는 long을 반환한다.

8.4.3 집합뷰

ConcurrentHashMap을 집합 뷰로 반환하는 keySet이라는 새 메서드를 제공한다. newKeySet이라는 새 메서드를 이용해 ConcurrentHashMap으로 유지되는 집합을 만들 수도 있다.

profile
내 안에 있는 힘을 믿어라.

0개의 댓글