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
을 적용한 결과로 각 항목의 값을 교체한다.이 메서드는
List
의replaceAll
과 비슷한 동작을 수행
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로 초기화되어 있으므로 Bifunction을 적용해 값이 증된다.
Last updated