독서 정리 - 모던 자바 인 액션
모던 자바 인 액션
[8장] 컬렉션 API 개선
8.1 컬렉션 팩토리
List<String> friends = Arrays.asList("1", "2", "3");
위와 같은 코드에서 add를 고정된 크기의 배열이라 실행하면 UnsupportedOperationException 발생
리스트를 인수로 받는 Hashset
생성자로 해결
stream API 사용
- 리스트 팩토리
List.of("a", "b", "c");
이거를 List의 팩토리 메서드라고 부른다.- List.of()에는 가변 인수를 받는 오버로딩은 존재하지 않다. 그 이유는 가변 인수는 추가 배열을 할당해서 리스트로 감싸는데 그 말은 즉슨 그 배열을 처리하는 비용까지 있는 것이다.
- 집합 팩토리
Set.of("a", "b", "c");
집합의 팩토리 메서드다.
중복된 요소 입력시 IllegalArgumentException 발생 - 맵 팩토리
Map.of("a", 1, "b", 2, "c", 3);
맵의 팩토리 메서드다. 키와 값을 번갈아 제공한다.
열 개 이상의 맵을 생성하는 경우에는Map.Entry<K, V>
객체를 인수로 받으며 가변 인수로 구현된Map.ofEntries(entry("a", 1), entry("b", 2), entry("c", 3));
과 같은 방법을 이용하는 것이 좋다.
위와 같은 팩토리로 생성된 컬렉션은 바꿀 수 없다!! set, add 등 메서드 사용 불가
8.2 리스트와 집합 처리
기존 컬렉션을 바꾸는 것은 에러를 유발하고 복잡해진다. 그래서 아래와 같은 메서드가 나왔다.
- removeIf 메서드
for(Transaction transaction : transactions) { if(Character.isDigit(transaction.getReferenceConde().charAt(0))) { transactions.remove(transaction); } }
위 코드는
ConcurrentModificationException
이 발생한다.
for-each에서는 Iterator 객체가 사용되는데 이 객체가 hasNext()로 Collection 객체 자체에게 묻고 있어서 상호간에 동기화가 되지 않는다.
이때 removeIf 메서드를 사용하면 좋다.transactions.removeIf(transaction -> Character.isDigit(transaction.getReferenceCode().charAt(0)));
- replaceAll 메서드
요소를 제거하는게 아니라 바꿔야할 때 사용하면 좋다.
stream을 사용하면 할 수 있지만, 새 컬렉션이 만들어진다는 점이 문제이다.
ListIterator 객체를 이용하면 set 메서드를 이용해 기존의 컬렉션을 바꿀 수 있다.
하지만 또 iterator.next()로 확인하며 set해나가기에는 복잡하고 문제가 일어날 수 있어 자바 8에서는replaceAll
메서드를 제공한다.examples.replaceAll(ex -> Character.toUpperCase(code.charAt(0))) + code.substring(1));
8.3 맵 처리
- forEach 메서드
for(Map.Entry<String, Integer> entry: examples.entrySet())
위와 같이 하던 맵 순회를 아래와 같이
BiConsumer
를 이용해 할 수 있다.examples.forEach((name, age) -> ~~)
- 정렬 메서드
Entry.comparingByValue, Entry.comparingByKey를 이용해 값 또는 키를 정렬할 수 있다.
examples.entrySet().stream().sorted(Entry.comparingByKey()).collect(Collectors.toMap())
와 같이 사용한다.
HashMap은 키로 생성한 해시코드로 접근할 수 있는 버켓에 저장하는 방법이 사용된다. 버킷이 커지면 정렬된 트리를 이용해 동적 치환한다. - getOrDefault 메서드
찾으려는 값이 존재하지 않으면 NPE 방지를 위해 null이 아닌 다른 값을 반환하도록 도와주는 메서드이다.
examples.getOrDefault("a", 0)
과 같이 사용할 수 있다. - 계산 패턴
- computeIfAbsent: 값이 없으면 새로 추가하고 계산한다. (이미 처리한 데이터 다시 계산안해도 될 때 유리)
- computeIfPresent: 값이 있으면 새 값을 계산하여 넣는다. null이 반환되면 맵에서 제거한다.
- compute: 새 값을 계산하고 저장
- 삭제 패턴
examples.remove("a", 1);
를 사용하면 제거된다. - 교체 패턴
- replaceAll: BiFunction을 이용해 모든 키의 값을 교체한다.
- replace: 키가 존재하면 값을 바꾼다.
- 합침
Map<String, Integer> f = Map.ofEntries(entry("a", 1), entry("b", 2)); Map<String, Integer> p = Map.ofEntries(entry("c", 3), entry("d", 4)); Map<String, Integer> sum = new HashMap<>(f); sum.putAll(p);
중복 값이 없을 때 위와 같이 합칠 수 있다.
중복 키가 다른 값으로 있으면merge
메서드를 사용할 수 있다.f.forEach((k, v) -> sum.merge(k, v, (age1, age2) -> age1 + "&" + age2));
이는 지정된 키와 연관 없거나 값이 null이면 null이 아닌 값과 연결하고, 결과가 null이면 항목을 제거한다.
8.4 개선된 ConcurrentHashMap
동시성 친화적인 HashMap이다. 동시 추가, 갱신을 허용한다. 스레드 안전한 맵!
- 리듀스와 검색
- forEach: 각 요소에 함수 실행
- reduce: 모든 값을 제공된 함수로 결과 생성
- search: null이 아닌 값일때까지 각 요소에 함수 적용
이는 상태를 잠그지 않고 수행하므로 변경될 수 있는 객체, 값, 순서 등에 의존하지 않아야 한다.
기본 값에는 특화된 메서드가 제공되므로 박싱 작업이 필요 없도록 적극 활용한다. - 키, 값으로 연산
- 키로 연산
- 값으로 연산
- Map.Entry 객체로 연산
위 네가지 연산 형태가 지원된다.
- 개수
mappingCount
메서드가 제공된다. size보다 사용하기가 장려된다. - 집합뷰
맵을 집합 뷰로 반환하는keySet
이라는 메서드를 제공한다.