Chapter 8 컬렉션 API 개선

·2023년 3월 13일
0

모던 자바 스터디

목록 보기
2/3
List<String> friends = new ArrayList<>();
friends.add("Raphael");
friends.add("Olivia");
friends.add("Thibaut");

세 문자를 리스트로 저장하려면 위와 같이 많은 코드가 필요하다.

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

Arrays.asList() 메서드를 사용하면 간단하게 줄일 수 있지만 추가, 삭제 등의 연산이 불가능하다.

그렇다면 집합은 어떻게 만들까?

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

컬렉션을 인수로 받는 HashSet의 생성자를 사용할 수 있다.

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

또는 스트림 API를 사용할 수 있다.

하지만 위의 두 방법은 내부적으로 불필요한 객체 할당이 필요하다.

Map을 만들고 싶으면??

List

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

자바9부터 추가된 List.of() 팩토리 메서드를 사용할 수 있다. 불변 리스트를 만든다.

List.of(E… elements) 로 다중 요소를 받을 수 있게 하지 않고, 위와 같이 오버로딩 했을까?

Set

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

집합의 경우도 팩토리 메서드가 있다. 중복된 요소가 있으면 IllegalArgumentException이 발생한다.

Map

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

Map.of()를 사용해서 Key와 Value를 번갈아 제공하는 방법이 있다.
List.of()와 마찬가지로 불변 객체가 만들어진다. 수정이 가능할까??

Map<String, Integer> ageOfFriends
	= Map.ofEntries(entry("Raphael", 30),
					entry("Olivia", 25),
					entry("Thibaut", 26));

Map.Entry<K, V> 객체를 사용하는 Map.ofEntries()로 위와 같이 각 엔트리를 넘길 수 있다. 가변 인수가 만들어지게 된다. 수정이 가능할까??

List와 Set 가공

* `removeIf(Predicate filter)`:  Predicate를 만족하는 모든 요소 제거
* `replaceAll(UnaryOperator operator)`: UnaryOperator로 요소를 변경
* `sort(Comparator c)`: 리스트를 정렬한다.

위의 메서드들은 컬렉션 상태를 변경한다. 왜 필요할까??

for(Apple apple: apples){
	if(apple.getColor().equals(Color.RED)){
		apples.remove(apple);
	}
}

사과 리스트를 순환하면서 빨간색 사과를 제거하는 코드이다. 무엇이 문제일까?
위 코드는 ConcurrentModificationException이 발생한다.

for-each 문은 컬렉션의 iterator()을 사용한다.
Iterator 객체와 List 컬렉션의 상태가 동기화되지 않는다.
이를 해결하려면 Iterator.remove()를 호출하면 된다.

for(Iterator<Apple> iterator = apples.iterator(); iterator.hasNext(); ){
	Apple apple = iterator.next();
	if(apple.getColor().equals(Color.RED)){
		iterator.remove();
	}
}

위와 같이 동기화 문제를 해결할 수 있다.

apples.removeIf(apple -> apple.getColor().equals(Color.RED));

이러한 상황에서 removeIf()를 사용하면 간단해지고 버그가 발생할 일도 줄어든다.

만약 제거하지 않고 리스트의 각 요소를 새로운 요소로 바꿔야 한다면??

apples.replaceAll(apple -> apple.setColor(Color.GREEN));

이런 식으로 모든 사과의 색을 초록색으로 변경할 수 있다.

Map 가공

forEach 메서드

for(Map.Entry<String, Integer> entry: ageOfFriends.entrySet()){
	String friend = entry.getKey();
	int age = entry.getValue();
	System.out.println(friend + " 의 나이: " + age);
}

맵의 모든 이름과 나이 쌍을 출력한다.

ageOfFriends.forEach((friend, age) 
	-> System.out.println(friend + " 의 나이: " + age));

자바 8부터 Map의 인터페이스는 forEach(BiConsumer consumer)를 지원하므로 간단하게 사용할 수 있다.

정렬 메서드

* Entry.comparingByValue()
* Entry.comparingByKey()
apples.entrySet().stream()
		.sorted(Entry.comparingByValue())
		.forEach(System.out::println);

위의 두 Comparator 팩토리를 사용해서 쉽게 정렬할 수 있다.

getOrDefault 메서드

Map<String, Integer> friends = new HashMap<>();
int result = friends.getOrDefault("연어", 5);

맵에서 해당하는 키가 없을 경우 null 체킹을 getOrDefault()를 사용해서 쉽게 할 수 있다.

계산 패턴

* computeIfAbsent: 제공된 키가 없거나 저장된 값이 null이라면, 키를 사용해서 새 값을 계산하고 맵에 추가
* computeIfPresent: 위와 반대의 경우라면, 새 값을 계산하고 맵에 추가
* compute: 제공된 키로 새 값을 계산하고 맵에 추가

맵의 키가 존재하는지 여부에 따라 계산 후 결과를 저장해야 할 때 사용

삭제 패턴

String key = "연어";
int value = 180;

if(friends.containsKey(key) && value==friends.get(key)){
	friends.remove(key);
}

Key와 Value가 모두 같은 지 확인하고 해당하는 Entry가 존재한다면 삭제하는 코드

friends.remove(key, value);

자바8부터 위와 같이 사용할 수 있다.

교체 패턴

* replaceAll: BiFunction을 적용해서 각 항목의 값을 교체
* replace: 키가 존재하면 값을 교체, 키가 특정 값일 때만 값을 교체하는 오버로드 버전도 존재

이전에 살펴본 List의 replaceAll()과 비슷하다.

합침

Map<String, String> family = Map.ofEntries(
										entry("Teo", "Star Wars"),
										entry("Christina", "James Bond"));

Map<String, String> everyone = Map.ofEntries(
										entry("Raphael", "Star Wars"));

everyone.putAll(family);

Map끼리 합칠 때에는 putAll()을 사용해서 위와 같이 합칠 수 있다. 이때 중복된 키가 없어야 한다.

Map<String, String> family = Map.ofEntries(
										entry("Teo", "Star Wars"),
										entry("Christina", "James Bond"));

Map<String, String> everyone = Map.ofEntries(
										entry("Teo", "Star Wars"),
										entry("Christina", "James Bond"));

family.forEach((k, v) ->
	everyone.merge(k, v, String::concat));

중복된 키가 있다면, merge()를 사용해서 병합 후 저장한다.
합치려는 대상의 key가 없거나 value가 null이면 주어진 v 값으로 저장한다.

HashMap<String, Integer> map = new HashMap<>();

map.merge("hi", 1, Integer::sum); //1
map.merge("hi", 1, Integer::sum); //2
map.merge("hi", 1, Integer::sum); //3

ConcurrentHashMap

내부 자료구조의 특정 부분만 락 -> 동시 추가 및 갱신 허용

* forEach: 각 entry에 주어직 액션을 실행
* reduce: 모든 entry를 reducing
* search: null이 아닌 값을 반환할 때까지 searching

위의 모든 메서드는 병렬 작업을 수행한다.
이때 parallelismThreshold 의 값이 한 스레드가 처리할 최대 entry 수 이다.

* 맵의 entry 갯수가 int 범위를 넘어갈 때는 `mappingCount()`
* 기존의 `keySet()`은 Map -> HashSet 으로 변경한다. ConcurrentHashSet으로 바꾸고 싶다면 `newKeySet()`을 사용한다.
profile
渽晛

0개의 댓글