일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- @Data
- @Getter
- 모던 자바 인 액션
- 쿠버네티스
- @EnableScheduling
- 검색 기능 확장
- 정렬
- 전화번호 목록
- 알고리즘
- Java
- 커링
- H-index
- @configuration
- 프로그래머스
- 영속 자료구조
- 해시
- 완주하지 못한 선수
- 고차원 함수
- kubenetes
- 롬복 어노테이션
- 코딩 테스트
- 스프링 스케쥴러
- K번째수
- 다리를 지나는 트럭
- 크론 표현식
- @Setter
- 가장 큰 수
- 루씬 인 액션
- 스택/큐
- 기능개발
- Today
- Total
Today I Learned
[모던 자바 인 액션] 8장. 스트림과 람다를 이용한 효과적 프로그래밍 본문
이 장의 내용
- 컬렉션 팩토리 사용하기
- 리스트 및 집합과 사용할 새로운 관용 패턴 배우기
- 맵과 사용할 새로운 관용 패턴 배우기
8.1 컬렉션 팩토리
자바에서 적은 요소를 포함하는 리스트를 어떻게 만들까?
다음 방법은 세 문자열을 저장하는데도 많은 코드가 필요하다.
List<String> friends = new ArrayList<>();
friends.add("Raphael");
friends.add("Olivia");
friends.add("Thibaut");
다음처럼 Arrays.asList() 팩토리 메서드를 이용해 코드를 줄일 수 있지만, 고정 크기의 리스트이기 때문에 요소를 추가하려하면 Unsupported OperationException이 발생한다.
List<String> friends = Arrays.asList("Raphael", "Olivia");
friends.set(0, "Rechard");
friends.add("Thibaut");
Unsupported OperationException 예외 발생
내부적으로 고정된 크기의 변환할 수 있는 배열로 구현되었기 때문에 이와같은 일이 일어난다.
리스트를 인수로 받는 HashSet 생성자를 사용하거나, 스트림 API로 해결할 수 있다.
Set<String> friends = new HashSet<>(Arrays.asList("Raphael", "Olivia","Thibaut"));
Set<String> friends = Stream.of("Raphael", "Olivia","Thibaut")
.collect(Collector.toSet());
하지만 두 방법 모두 매끄럽지 못하며 불필요한 객체 할당을 필요로 한다.
8.1.1 리스트 팩토리
List.of 팩토리 메서드를 이용하면 간단하게 리스트를 만들 수 있다.
List<String> friends = List.of("Raphael", "Olivia","Thibaut");
하지만 마찬가지로 변경할 수 없는 리스트이기 때문에 add()나 set() 메서드를 사용할 수 없다.
8.1.2 집합 팩토리
List.of와 비슷한 방법으로 바꿀 수 없는 집합을 만들 수 있다.
Set<String> friends = Set.of("Raphael", "Olivia","Thibaut");
8.1.3 맵 팩토리
Map.of 팩토리 메서드에 키와 값을 번갈아 제공하는 방법으로 맵을 만들 수 있다.
Map<String, Integer> ageOfFriends = Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26);
10개 이상의 키와 값 쌍을 가진 맵을 만들때는 Map.Entry<K, V> 객체를 인수로 받는 Map.ofEntries 팩토리 메서드를 이용하는 것이 좋다.
import static java.util.Map.entity;
Map<String, Integer> ageOfFrieds = Map.ofEntries(
entry("Raphael", 30),
entry("Olivia", 25),
entry("Thibaut", 26));
8.2 리스트와 집합 처리
자바 8에서는 List, Set 인터페이스에 다음과 같은 메서드를 추가했다.
- removeIf : 프레디케이트를 만족하는 요소를 제거한다.
- replaceAll : UnaryOperator 함수를 이용해 요소를 바꾼다. 리스트에서 사용할 수 있다.
- sort : 리스트를 정렬한다.
이 메서드는 새로운 결과를 만드는 것이 아니라 호출한 기존 컬렉션 자체를 바꾼다.
8.2.1 removeIf 메서드
다음은 숫자로 시작되는 참조 코드를 가진 트랜잭션을 삭제하는 코드다.
for(Transaction transaction : transactions) {
if(Character.isDigit(transaction.getReferanceCode().charAt(0))) {
transaction.remove(transaction);
}
}
코드를 실행해보면 ConcurrentModificationException을 일으킨다.
forEach 루프는 Iterator 객체를 사용하므로 위 코드는 다음과 같이 해석된다.
for(Iterator<Transaction> iterator = transactions.iterator(); iterator.hasNext(); ) {
Transaction transaction = iterator.next();
if(Character.isDigit(transaction.getReferanceCode().charAt(0))) {
transactions.remove(transaction);
}
}
Iterator 객체를 통해 소스를 질의하고, Collection 객체 자체에 remove()를 호출해 요소를 삭제하고있다. 따라서 반복자의 상태와 컬렉션의 상태가 동기화되지 않는다.
Iterator 객체를 명시적으로 사용하고 그 객체의 remove() 메서드를 호출해줘야 정상적으로 동작한다.
for(Iterator<Transaction> iterator = transactions.iterator(); iterator.hasNext(); ) {
Transaction transaction = iterator.next();
if(Character.isDigit(transaction.getReferanceCode().charAt(0))) {
iterator.remove();
}
}
위 코드 패턴은 자바8의 removeIf 메서드로 바꿀 수 있다. removeIf는 삭제할 요소를 가리키는 프레디케이트를 인수로 받는다.
transactions.removeIf(transaction ->
Character.isDigit(transaction.getReferenceCode().charAt(0)));
8.2.2 replaceAll 메서드
리스트의 각 요소를 새로운 요소로 바꾸고자할때 스트림 API를 사용하면 다음과 같다.
referenceCode.stream().map(code -> Character.toUpperCase(code.charAt(0)) + code.subString(1))
.collect(Collectors.toList())
.forEach(System.out::println);
하지만 이 코드는 새 문자열 컬렉션을 만든다. 기존 컬렉션을 바꾸려면 ListIterator 객체를 이용해야한다.
for(ListIterator<String> iterator = referenceCodes.listIterator(); iterator.hasNext(); ) {
String code = iterator.next();
iterator.set(Character.toUpperCase(code.charAt(0)) + code.subString(1));
}
코드가 복잡해졌다. 그리고 이처럼 컬렉션 객체와 Iterator 객체를 혼용하면 반복과 변경이 동시에 이루어져 쉽게 문제를 일으킨다.
자바 8의 replaceAll 메서드를 사용하면 간단하게 구현할 수 있다.
referenceCodes.replaceAll(code -> Character.toUpperCase(code.charAt(0)) + code.subString(1));
8.3 맵 처리
8.3.1 forEach 메서드
맵에서 키와 값을 반복하면서 확인하는 작업은 번거롭다.
for(Map.Entity<String, Integer> entry : ageOfFriends.entitySet()) {
String friends = entry.getKey();
Integer age = entry.getValue();
System.out.println(friend + " is " + age + " years old");
}
자바8의 Map 인터페이스는 BiConsumer(키와 값을 인수로 받음)를 인수로 받는 forEach 메서드를 지원한다.
ageOfFrends.forEach((friends, age) -> System.out.println(friend + " is " + age + " years old"));
8.3.2 정렬 메서드
새로 추가된 유틸리티를 이용해 맵의 항목을 값 또는 키를 기준으로 정렬할 수 있다.
- Entry.comparingByKey
- Entry.comparingByValue
Map<String, String> favouriteMovies = Map.ofEntries(
entry("Raphael", "Star Wars"),
entry("Cristina", "Matrix"),
entry("Olivia", "James Bond"));
favouriteMovies.entrySet().stream()
.sorted(Entry.ComparingByKey())
.forEachOrdered(System.out::println);
/*
출력
cristina=matrix
olivia=james Bond
Raphael=Star Wars
*/
8.3.3 getOutDefault 메서드
이 메서드는 첫 번째 인수로 키를, 두 번째 인수로 기본값을 받으며 맵에 키가 존재하지 않으면 두 번째 인수로 받은 기본값을 반환한다. 요청결과를 확인하지 않아도 NullPointerException 문제를 해결할 수 있다.
Map<String, String> favouriteMovies = Map.ofEntries(
entry("Raphael", "Star Wars"),
entry("Olivia", "James Bond"));
System.out.println(favorieMovies.getOrDefault("Olivia", "Matrix")); //James Bond 출력
System.out.println(favorieMovies.getOrDefault("Thibaut", "Matrix")); //Matrix 출력
8.3.4 계산 패턴
맵에 키가 존재하는지 여부에 따라 동작을 수행하고자 할 때에 사용할 수 있는 연산이 있다.
- computeIfAbsent : 제공된 키에 해당하는 값이 없으면, 키를 이용해 새 값을 계산하고 맵에 추가한다.
- computeIfPresent : 제공된 키가 존재하면 새 값을 계산하고 맵에 추가한다.
- comput : 제공된 키로 새 값을 계산하고 맵에 저장한다.
8.3.5 삭제 패턴
키가 특정한 값일 경우 항목을 제거하도록 remove 메서드를 사용할 수 있다.
String key = "Raphael";
String value = "Jack Reacher 2";
favoriteMovies.remove(key, value);
8.3.6 교체 패턴
맵의 항목을 바꾸는데 사용할 수 있는 두 개의 메서드가 추가되었다.
- replaceAll : BiFunction을 적용한 결과로 각 항목의 값을 교체한다.
- Replce : 키가 존재하면 맵의 값을 바꾼다. 키가 특정 값으로 매핑되었을 때만 값을 교체하는 오버로드 버전도 있다.
//repaceAll을 적용할 것이므로 바꿀 수 있는 맵을 사용해야 한다.
Map<String, String> favouriteMovies = new HashMap<>();
favouriteMovies.put("Raphael", "Star Wars"),
favouriteMovies.put("Olivia", "James Bond");
favoriteMovies.replaceAll((friend, movie) -> movie.toUpperCase());
8.3.7 합침
맵을 합칠때는 putAll을 사용할 수 있다.
Map<String, String> family = Map.ofEntries(
entry("Teo", "Star Wars"),
entry("Cristina", "James Bond"));
Map<String, String> friens = Map.ofEntries(
entry("Raphael", "Star Wars"));
Map<String, String> everyone = newHashMap<>(family);
everyone.putAll(friends);
중복된 키가 있다면 merge 메서드를 이용할 수 있다. 이 메서드는 중복된 키를 어떻게 합칠지 결정하는 BiFunction을 인수로 받는다.
Map<String, String> everyone = newHashMap<>(family);
friends.forEach((k, v) ->
everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2));
8.4 개선된 ConcurrentHashMap
ConcurrentHashMap는 병렬 친화적인 HashMap이다. 내부 자료구조의 특정 부분만 잠궈 동시 추가, 갱신 작업을 허용한다.
8.4.1 리듀스와 검색
ConcurrentHashMap은 스트림과 비슷한 세 가지 새로운 연산을 지원한다.
- forEach : 각 (키,값) 쌍에 주어진 액션을 실행
- reduce : 모든 (키,값) 쌍을 제공된 리듀스 함수를 이용해 결과로 합침
- search : 널이 아닌 값을 반환할 때까지 각 (키,값) 쌍에 함수를 적용
다음처럼 키, 값, Map.entry 인수를 이용해 네 가지 연산 형태를 지원한다.
- 키, 값으로 연산 (forEach, reduce, search)
- 키로 연산 (forEachKey, reduceKeys, searchKeys)
- 값으로 연산 (forEachValue, reduceValues, searchValues)
- Map.Entry 객체로 연산(forEachEntry, reduceEntries, searchEntries)
이들 연산은 ConcurrentHashMap의 상태를 잠그지 않고 연산을 수행하므로, 연산에 제공한 함수는 계산이 진행되는 동안 바뀔 수 있는 객체, 값, 순서 등에 의존하지 않아야 한다.
또한 병렬성 기준값(threshhold)를 지정해야 하는데, 맵의 크기가 주어진 기준값보다 작으면 순차적으로 연산을 실행한다. 기준값이 1일때는 공통 스레드 풀을 이용해 병렬성을 극대화하며, 기준값이 Long.MAX_VALUE일때는 한 개의 스레드로 연산을 실행한다.
이 예제에서는 reduceValues 메서드를 이용해 맵의 최댓값을 찾는다.
ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<>();
long parallelismThreshold = 1;
Optional<Integer> maxValue = Optional.ofNullable(map.reduceValues(parallelismThreshold, Long::max));
8.4.2 계수
ConcurrentHashMap 클래스는 맵의 매핑 개수를 반환하는 mappingCount 메서드를 제공한다. size 대신 mappingCount 를 사용해야 매핑의 개수가 int의 범위를 넘어서는 이후의 상황을 대처할 수 있다.
8.4.3 집합뷰
ConcurrentHashMap을 집합 뷰로 반환하는 keySet 메서드를 제공한다. 맵을 바꾸면 집합도 바뀌고 집합을 바꾸면 맵도 영향을 받는다.
newkeySet 메서드를 이용하면 ConcurrentHashMap으로 유지되는 집합을 만들 수도 있다.
'JAVA & Spring > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] 11장. null 대신 Optional 클래스 (0) | 2021.09.06 |
---|---|
[모던 자바 인 액션] 9장. 리팩터링, 테스팅, 디버깅 (0) | 2021.09.01 |
[모던 자바 인 액션] 7장. 병렬 데이터 처리와 성능 (2) (0) | 2021.08.12 |
[모던 자바 인 액션] 7장. 병렬 데이터 처리와 성능 (1) (0) | 2021.08.04 |
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집 (2) (0) | 2021.07.28 |