Ch 08. 컬렉션 API 개선

8.1 컬렉션 팩토리

  • 컬렉션 팩토리로 만든 컬렉션은 변경할 수 없다. 새 요소를 추가하거나 요소를 삭제할 수 없다.

    • 변경하려고 하면 UnsupportedOperationException이 발생한다.

    • null 요소는 금지한다.

  • 리스트를 바꿔야하는 상황이라면 직접 리스트를 만들면 된다.

// 1. Arrays.asList 팩토리 메서드
List<String> friends = **Arrays.asList**("solar", "holar");

// 2. Arrays.asSet 팩토리 메서드는 없다. 대신, 리스트를 인수로 받는 HashSet 생성자를 사용
Set<String> friends = new HaashSet<>(**Arrays.asList**("solar", "holar"));

8.1.1 리스트(List) 팩토리

// 1. List.of 팩토리 메서드 이용
List<String> friends = **List.of**("solar", "holar");
friends.add("colar"); //UnsupportedOperationException 발생. 요소 변경 불가

// 2. 스트림 API 이용
Set<String> friends = Stream.of("solar", "holar")
														.**collect(Collections.toList())**;

데이터 처리 형식을 설정하거나 데이터를 변환할 필요가 없다면 사용하기 간편한 팩토리 메서드를 이용할 것을 권장

8.1.2 집합(Set) 팩토리

Set.of 팩토리 메서드를 이용해서 바꿀 수 없는 집합을 만들 수 있다.

// 1. Set.of 팩토리 메서드 이용
Set<String> friends = **Set.of**("solar", "holar", "holar"); //중복요소 -> IllegalArgumentException 발생
Set<String> friends = **Set.of**("solar", "holar");

8.1.3 맵(Map) 팩토리

자바 9 에서는 두 가지 방법으로 바꿀 수 없는 맵을 만들 수 있다.

// 1. Map.of 팩토리 메서드 이용 - key, value를 번갈아서 제공
Map<String, Integer> ageOfFriends = Map.of("solar", 30, "holar", 28);

// 2. Map.ofEntries 팩토리 메서드 이용 - Map.Entry<K, V> 객체를 인수로 받음
// Map.entry는 Map.Entry 객체를 만드는 팩토리 메서드
import static java.util Map.entry;
Map<String, Integer> ageOfFriends = Map.ofEntries(entry("solar", 30),
																									entry("holar", 28));

8.2 리스트와 집합 처리

  • removeIf

    • 프레디케이트를 만족하는 요소를 제거한다.

    • List나 Set을 구현하거나 그 구현을 상속받은 모든 클래스에서 이용할 수 있다.

  • replaceAll

    • 리스트에서 이용할 수 있는 기능으로 UnaryOperator 함수를 이용해 요소를 바꾼다.

  • sort

    • List 인터페이스에서 제공하는 기능으로 리스트를 정렬한다.

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

8.2.1 removeIf 메서드

//예제) 숫자로 시작되는 참조 코드를 가진 트랜잭션을 삭제
transactions.removeIf(transaction -> 
			Character.isDigit(transaction.getReferenceCode().chatAt(0)));

8.2.2 replaceAll 메서드

  • 리스트의 각 요소를 새로운 요소로 바꿀 수 있다.

방법 1. ListIterator 객체(요소를 바꾸는 set() 메서드를 지원)를 이용해서 기존 컬렉션의 요소를 변경

for (ListIterator<String> iterator = referenceCodes.listIterator();
			iterator.hasNext(); ) {
		 String code = iterator.next();
		 iterator.set(Character.toUpperCase(code.charAt(0)) + code.substring(1));
}

→ 복잡…

방법 2. replaceAll 메서드 이용

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

8.3 맵 처리

8.3.1 forEach 메서드

맵에서 키와 값을 반복하면서 확인하는 작업은 귀찮은 작업이다.

**Map.Entry<K,V>**의 반복자를 이용해 맵의 항목 집합을 반복할 수 있다.

for(Map.Entry<String, Integer> entry>: ageOfFriends.entrySet()) {{{
	String friend = entry.getKey();
	Integer age = entry.getValue();
	// ..
}

자바 8에서부터 Map 인터페이스는 Biconsumer(키와 값을 인수로 받음)를 인수로 받는 forEach 메서드를 지원

										//key, value
ageOfFriends.forEach((friend, age) -> System.out.println( ... ));

8.3.2 정렬 메서드

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

  • Entry.comparingByValue

  • Entry.comparingByKey

ap<String, String> favoriteMovies = Map.ofEntries(
        Map.entry("ljo", "Star Wars"),
        Map.entry("hsy", "Matrix"),
        Map.entry("yhh", "James Bond")
);

favoriteMovies.entrySet().stream()
		.sorted(Entry.comparingByKey())  // 키 값 순서대로 (사람 이름을 알파벳 순서대로)
		.forEachOrdered(System.out::println);

---------------------------------------------------------
hsy=Matrix
ljo=Star Wars
yhh=James Bond

8.3.3 getOrDefault 메서드

기존에 찾으려는 키가 존재하지 않을 경우 NPE을 방지하기 위해 널 체크를 해야 했지만, getOrDefault 메서드를 이용하면 키가 존재하지 않으면 기본값을 반환하는 방식으로 NullPointerException을 방지할 수 있다.

  • 첫 번째 인수로 받은 가 맵에 없으면

  • 두 번째 인수로 받은 기본값 을 반환한다.

  • 키가 존재하더라도 값이 널인 상황에서는 널을 반환할 수 있으므로 주의

8.3.4 계산 패턴

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

  • computeIfAbsent

    • 제공된 키에 해당하는 값이 없으면(null도 포함), 키를 이용해 새 값을 계산하고 맵에 추가한다.

  • computeIfPresent

    • 제공된 키가 존재하면 새 값을 계산하고 맵에 추가한다.

    • 주의) 값을 만드는 널을 반환하면 현재 매핑을 맵에서 제거한다.

  • compute

    • 제공된 키로 새 값을 계산하고 맵에 저장한다.

Map<K, List<V>>에 요소를 추가하려면 항목이 초기화되어있는지 확인해야할 때 computeIfAbsent 를 이용하면 유용하다.

ex) Raphael에게 줄 영화 목록을 만든다고 가정

  • 기존 코드

String friend = "Raphael";
List<String> movies = friendsToMovies.get(friend);
if (movies == null){     // 초기화 확인
	movies = new ArrayList<>();
	friendsToMovies.put(friend, movies);
}
movies.add("Iron man"); // 영화 추가
  • 컬렉션 API 사용

friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>)).add("Star Wars");

8.3.5 삭제 패턴

  • 제공된 키에 해당하는 맵 요소를 제거하는 remove 메서드는 이미 알고 있다

    • 삭제할 경우 키가 존재하는지 확인하고 값을 삭제하지만

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

movies.remove(key, value);

8.3.6 교체 패턴

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

  • replaceAll

    • Bifunction 을 적용한 결과로 각 항목의 값을 교체한다.

    • 이 메서드는 ListreplaceAll 과 비슷한 동작을 수행

  • Replace

    • 키가 존재하면 맵의 값을 바꾼다.

    • 키가 특정 값으로 매핑되었을 때만 값을 교체하는 오버로드 버전 도 있다.

8.3.7 합침

  • putAll : 두 개의 맵을 합침

    • targetMap.putAll(sourceMap)

  • 두 개의 맵에서 값을 합칠 때 조건을 걸고 합치려면 merge 메서드 이용

    • merge 메서드는 중복된 키를 어떻게 합칠지 결정하는 BiFunction을 인수로 받는다.

Map<String, String> family = Map.ofEntries(
	entry("Teo", "Star Wars"), entry("Cristina", "James Bond")
);
Map<String, String> friends = Map.ofEntries(
	entry("Raphael", "Star Wars"), entry("Cristina", "Matrix")
);

// family와 friends에 중복된 키가 존재한다. 중복된 키를 어떻게 합칠지 결정하는 BiFunction을 인수로 받는다.
// merge 메서드 사용 - 조건에 따라 맵을 합치는 코드
Map<String, String> everyone = new HashMap<>(family);
friends.forEach((k, v) ->
	everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2) //중복된 키가 있으면 두 값을 연결
);

{Raphael=Star Wars, Cristina=James Bond & Matrix, Teo=Star Wars}
  • merge 를 이용해 초기화 검사를 구현할 수 있다.

    예시 ) 영화를 몇 회 시청했는지 기록하는 맵에 시청횟수를 값을 증가시키지 전에 관련 영화가 이미 맵에 존재하는지 확인

    moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L);
    • “키와 연관된 기존 값에 합쳐질 널이 아닌 값 또는 값이 없거나 키에 널 값이 연관되어 있다면 이 값을 키와 연결”

      1. 키의 반환값이 널이므로 처음에는 1이 사용된다.

      2. 그 다음부터는 값이 1로 초기화되어 있으므로 Bifunction을 적용해 값이 증된다.

Last updated