# Ch 08. 컬렉션 API 개선

## 8.1 컬렉션 팩토리

* **컬렉션 팩토리로 만든 컬렉션은 변경할 수 없다.** 새 요소를 추가하거나 요소를 삭제할 수 없다.
  * 변경하려고 하면 **UnsupportedOperationException**이 발생한다.
  * null 요소는 금지한다.
* 리스트를 바꿔야하는 상황이라면 직접 리스트를 만들면 된다.

```java
// 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) 팩토리

```java
// 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 팩토리 메서드를 이용해서 바꿀 수 없는 집합을 만들 수 있다.

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

### 8.1.3 맵(Map) 팩토리

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

```java
// 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 메서드

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

### 8.2.2 replaceAll 메서드

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

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

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

→ 복잡…

방법 2. replaceAll 메서드 이용

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

## 8.3 맵 처리

### 8.3.1 forEach 메서드

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

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

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

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

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

### 8.3.2 정렬 메서드

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

* `Entry.comparingByValue`
* `Entry.comparingByKey`

```java
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에게 줄 영화 목록을 만든다고 가정

* 기존 코드

```java
String friend = "Raphael";
List<String> movies = friendsToMovies.get(friend);
if (movies == null){     // 초기화 확인
	movies = new ArrayList<>();
	friendsToMovies.put(friend, movies);
}
movies.add("Iron man"); // 영화 추가
```

* 컬렉션 API 사용

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

### 8.3.5 삭제 패턴

* 제공된 키에 해당하는 맵 요소를 제거하는 `remove` 메서드는 이미 알고 있다
  * 삭제할 경우 키가 존재하는지 확인하고 값을 삭제하지만
* `자바 8` 에서는 키가 특정한 값과 연관되어 있을 때만 항목을 제거하는 `오버로드 버전` 메서드를 제공한다.

```
movies.remove(key, value);
```

### 8.3.6 교체 패턴

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

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

### 8.3.7 합침

* `putAll` : 두 개의 맵을 합침
  * targetMap.putAll(sourceMap)
* 두 개의 맵에서 값을 합칠 때 `조건`을 걸고 합치려면 `merge` 메서드 이용
  * merge 메서드는 중복된 키를 `어떻게 합칠지 결정하는 BiFunction`을 인수로 받는다.

```java
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` 를 이용해 초기화 검사를 구현할 수 있다.

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

  ```java
  moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L);
  ```

  * “키와 연관된 기존 값에 합쳐질 널이 아닌 값 또는 값이 없거나 키에 널 값이 연관되어 있다면 이 값을 키와 연결”
    1. 키의 반환값이 널이므로 처음에는 1이 사용된다.
    2. 그 다음부터는 값이 1로 초기화되어 있으므로 Bifunction을 적용해 값이 증된다.
