http://www.yes24.com/Product/Goods/77125987
[10.20 - 11.08]
1장 [자바 8 - 11] 까지의 상황에 대하여
메서드를 값으로 전달 한다
스트림의 외부/내부 반복
컬렉션 > 스트림 > 병렬처리 >List + 복원
2장 동작 파라미터화
동작 파라미터화 - 아직 어떻게 실행할지 정의하지 안은 코드 블록을 의미한다.
해당 블록은 나중에 프로그램에서 호출하며 실행은 나중으로 미뤄지는것이다.
책에서는 Apple을 분류하는 방식으로 진행하지만 개인 공부를 위해 따로 코드를 작성했다.
검색 조건 : 나이, 이름 성별로 진행한다.
위의 코드에서 Predicate의 객체의 선언하는 이유는 메서드가 객체만 인수로 받기 때문이다.
test의 메서드를 구현하고 불리언 표현식을 전달할 수 있기에 이 동작은 '코드를 전달'할 수 있는 것이나 다름없다.
책에서는 동작 파라미터를 사용하는 이유를 변화하는 요구 사항에 잘 대응하기 위해서이라고 한다.
또한 각 항목에 적용할 동작을 분리할 수 있다는게 동작 파라미터화의 강점이다.
기존에는 파라미터들을 넘겨서 처리하는 방식으로 진행했었는데 '부모는 자식을 품을 수 있다'를 생각한다면... 분명 맞는 말이다.
다만 아쉬운점은 파라미터로 넘기는 행위가 간소화 되지 않는 코드는 오히려 길게 느껴진다는 부분이다. (어려우면서도 신기하다)
3장 람다
람다의 표현식은 익명 클래스와 비슷하며 표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다.
위에서는 람다가 인수를 자신의 바디안에서만 사용했지만
자유함수 (파라미터안의 변수가 아닌 외부변수)도 활용할 수 있다. 이 행동을 람다 캡처링이라고 한다.
자유함수를 사용할때 제약사항이 존재한다.
사용 하는 변수가 실질적/명시적으로 final 상태이어야 한다는 점이다.
+ 메소드 참조
기존의 Member를 나이별로 sort 한다.
3장 람다... 보다는 메소드 참조, 그리고 Comperator, Predicate... 유틸들에 조금더 집중한 기분이다...
다른 유틸은 아직 잘 모르겠지만 비교자는 잘 사용할거 같다
4장 스트림 소개
스트림의 특징을 짧게 보고 넘어가자
- 선언형으로 구현이 가능하다
- 동작 수행으로 처기 가능 (if문 제어 X)
- 요구사항에 대응하기 쉽다.
위의 lowCaloriesDishesName을 통해서 우리는 stream이 filter > sorted > map으로 선언 파이프라인 형식으로 되어있음을 확인할 수 있다. 또한 스트림을 통해서 가독성, 유연성, 성능까지 챙길 수 있다.
우리가 흔히 생각하는 컬렉션과 스트림의 차이 확인해보자
스트림
- '데이터 처리 연산을 위해 소스에서 추출된 연속된 요소'
- filter, sort, map과 같이 표현 계산식이 메인이다.
- 스트림 연산은 파이프라인 형식을 구성할 수 있도록 자기 자신을 반환한다.
- 내부 반복을 지원한다.
- 한 번만 탐색할 수 있다.
- 사용자 요청시에만 요소를 계산한다. 스트림은 게으른 컬렉션이다.
컬렉션
- 반복 탐색이 가능하다.
- 외부 반복으로 사용자가 직접 반복자를 붙여줘야 한다. (ex: for-each)
- 자료구조로 시간, 공간의 복잡성 그리고 관련된 요소 저장 및 접근 연산에 집중 되어있다.
- 현재 자료구조가 포함하는 모든 요소들을 저장하며 적극적으로 생성한다.
threeHighCaloricDishNames 의 파이프라인 중 < filter, map, limit>까지를 우리는 중간 연산, 그리고 collect은 최종 연산이라고 부른다.
중간 연산의 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지 아무런 연산을 수행하지 않는다는 것이다.
중간 연산을 다 합친 후 그 연산을 최종 연산으로 한번에 처리한다.
코드를 통해서 확인해보자
스트림의 게으른 특성이다. 이를 통해서 우리는 최적화 효과를 얻을 수 있다. 또한 3개만 나온것은 limit연산 (쇼트서킷 기법)이다.
log를 통해서 알 수 있듯이 filter, map은 현재 하나의 과정으로 병합되었으며 이를 루프 퓨전이라고 한다.
5장 스트림 활용
4장에 이어서 스트림에서 활용하는 API들을 소개한다.
takeWhile, dropWhile
limit
skip
map, flatMap
reduce 연산 : 스트림 모든 요소를 처리해서 값으로 도출하는 작업
range, rangeClosed
현재 flatMap의 상용 방법이 제일 헷갈리는 상황이다.. 따로 정리해야겠다.
+)
https://winter1396love.tistory.com/66
6장 스트림 데이터 수집
toList | List<T> | 스트림의 모든 항목을 리스트로 수집 |
toSet
|
Set<T>
|
스트림의 모든 항목을 집합으로 수집
|
toCollection
|
Collection<T>
|
스트림의 모든 항목을 발행자가 제공하는 컬렉션으로 수집
|
counting
|
Long
|
스트림 항목 수 계산
|
summingInt
|
Integer
|
스트림 항목의 정수 프로퍼티 값 더함
|
averagingInt
|
Double
|
스트림 항목의 정수 프로퍼티 평균값 계산
|
summarizingInt
|
IntSummaryStatistics
|
최댓값, 최솟값, 합계, 평균 등의 정수 정보 통계 수집
|
joining
|
String
|
스트림 각 항목에 toString 메서드를 호출한 결과 문자열 연결
|
maxBy
|
Optional<T>
|
최댓값 요소를 Optaionl로 감싼 값으로 반환. 요소가 없을때는 Optional.empty()반환
|
minBy
|
Optional<T>
|
최소값 요소를 Optaionl로 감싼 값으로 반환. 요소가 없을때는 Optional.empty()반환
|
reducing
|
produced by the reduction operation
|
누적자를 초깃값으로 설정한 다음에 BinaryOperator로 스트림의 각 요소를 반복적으로 누적자와 합쳐 스트림을 하나의 값으로 리듀싱
|
collectingAndThen
|
returned by transforming function
|
다른 컬렉터를 감싸고 그 결과에 변환 함수 적용
|
groupingBy
|
Map<K, List<T>>
|
하나의 프로퍼티값을 기준으로 스트림의 항목을 그룹화하여 기준 프로퍼티값을 키로 사용
|
partitionBy
|
Map<Boolean, List<T>>
|
Predicate를 스트림의 각 항목에 적용한 결과로 항목 분할
|
summingInt
summarizingInt
joining
groupingBy
- 스트림 요소를 하느이 값으로 리듀스, 요약
- 요소 그룹화
- 요소 분할
- 연산을 바킷 (BUCKET)에 담는다고 생각하면 편하다
partitionBy - 불린 반환, flase/true 두 요소 모두 유지
8장 컬렉션 API
Collection의 remove 동작으로 발생한 에러 UnsupportedOperationException을 보고 넘어가자
우선 removeIf()로 List내의 mick를 제거하고자 삭성한 코드이다.
우릐 코드로 발생한 에러
지원하지 않은 방식이라고 한다... 그럼 인텔리제이에서 주는 힌트를 적용해서 재시도 해보자
기존List를 ArrayList로 감싸고 실행해보자
성공이다.
List.of를 조금 더 탐구해보자
해당 메소드 설명에서 우리는 아래의 설명을 확인할 수 있다.
위와 같이 Elements cannot br added, removed or replace라고 명시 되어있다.
replaceAll
forEach,
comparingByValue,
comparingByKey,
getOrDefault
compute : 제공된 키로 새 값을 계산하고 맵에 저장한다.
computeIfAbsent : 제공된 키가 없으면 compute 실행
computeIfPresent : 제공된 키가 있으면 compute 실행
putAll : 중복 없을시
merge : 중복 있으면 유연하게 매칭 됨
ConcurrentHashMap : 동시성 친화적, 내부 자료구조의 특정 부분만 잠궈서 동시 추가, 갱신 작업을 허용한다.
해당 ConcurrentHashMap은 세가지 연산을 지원한다.
키, 값으로 연산 | forEach, reduce, search |
키로 연산 | forEachKey, reduceKey, searchKey |
값으로 연산 | forEachValue, reduceValues, searchValues |
Map, Entry 객체로 연산 | forEachEntry, reduceEntries, searchEntries |
해당 HashMap의 상태를 잠그지 않고 연산을 수행한다는 점에서 계산이 진행되는 동안 바뀔 수 있는 객체, 값, 순서 등에 의존해서는 안된다.
연산의 병렬성 기준값도 지정해야한다.
ConcurrentHashMap은 매핑 개수를 반환하는 mappingCount, 집합뷰를 반환하는 keySet 메서드를 제공한다.
+) ConcurrentHashMap는 아래 링크를 추가로 확인하자.
https://pplenty.tistory.com/17
9장 [리팩터링,테스팅, 디버깅]
10장 [람다를 이용한 도메인 전용 언어]
두 챕터는 람다를 이용한 리팩토링, DSL을 이야기한다.
람다 표현식으로 불필요한 코드 제거가 가능한 점
DSL의 주요 기능은 개발자와 도메인 전문가의 간격을 좁히는것에 있다
자바에서는 Stream, Collectors과 같은 정렬, 필터링, 변환, 그룹화에 유능한 API를 제공한다.
자바 SQL mapping 도구 jOOQ, BDD 프레임워크 큐컴버, 엔터프라이즈 등 스프링 확장도 존재한다.
개인적으로 리펙토링으로 람다 형식으로 많은 코드를 변경하면서 개발자 보기 편한 방향으로 바꾼다만 알고 있었는데
이 행위가 DSL과도 관계가 있다는 점에서 좀 신기했다,,!
11장 null 대신 Optional 클래스
Optional
.get (가장 위험함)
.orElse
.orElseGet
.orElseThrow
.ifPresent
.ifPresentOrElse
.empty
.filter
.flatMap
.map
.stream
.ofNullable
12장 날짜와 시간 API
JAVA 유틸에서 제공해주는 클래스가 존재한다.
LocalDate, LocalTime, LocalDateTime, Instant, Duration, Period
Instant는 기계의 날짜를 계산하며 초와 나노초 정보를 제공하기에 사람이 읽을 수 있는 정보를 제공하지 않는다.
Duration은 초와 나노초로 시간 단위를 표현하기에 between 메서드에 LocalDate 정보 전달이 불가능하다.
그래서 Period로 처리한다.
메서드 | 정적 | 설명 |
between | Y | 두 시간 사이의 간격 생성 |
from | Y | 시간 단위로 간격 생성 |
of | Y | 주어진 구성 요소에서 간격 인스턴스 생성 |
parse | Y | 문자열 파싱, 간격 인스턴스 생성 |
addTo | N | 현재값의 본사본 생성 후 다음에 지정된 Temporal 객체에 추가함 |
get | N | 현재 간격 정보값을 읽음 |
isNegative | N | 간격이 음수인지 확인 |
isZero | N | 간격이 0인지 확인 |
minus | N | 현재값에서 주어진 시간을 뺀 복사본을 생성함 |
multipliedBy | N | 현재값에서 주어진 값을 곱한 복사본을 생성함 |
negated | N | 주어진 값의 부호를 반전한 복사본 생성 |
plus | N | 현재값에 주어진 시간을 더한 복사본 생성 |
substractFrom | N | 저장된 Temporal 객체에서 간격을 뺌 |
우리가 LocalDate에서 특정 날짜에 조작이 필요한 경우
메서드 | 정적 | 설명 |
from | Y | 주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성함 |
now | Y | 시스템 시계로 Temporal 객체 생성 |
of | Y | 주어진 구성요소에서 Temporal 객체의 인스턴스를 생성함 |
parse | Y | 문자열을 파싱해서 Temporal 객체 생성 |
atOffSet | N | 시간대 오프셋과 Temporal 객체 합침 |
atZone | N | 시간대 오프셋과 Temporal 객체 합침 |
format | N | 지정된 포맷으로 Temporal 객체를 문자열로 변환함 |
get | N | Temporal 객체 상태를 읽음 |
minus | N | 특정 시간을 뺀 Temporal을 생성 |
plus | N | 특정 시간을 더한 Temporal을 생성 |
with | N | 일부 상태를 바꾼 Temporal을 생성 |
위의 상황은 간단한 3일 더하기 1년빼기와 같은 동작들이다. 만약에 다음주 일요일, 돌아오는 평일 또는 특정 달의 마지막 날을 구하고 싶을때는 어떻게 처리 할까?
TemporalAdjusters를 사용해주자
메서드 | 설명 |
dayOfWeekInMonth | 서수 요일에 해당하는 날짜를 반환 |
firstDayOfMonth | 현재 달의 첫 번째 날짜를 반환 |
firstDayOfNextMonth | 다음 달의 첫 번째 날짜를 반환 |
firstDayOfNextYear | 내년의 첫 번째 날짜를 반환 |
firstDayOfYear | 올해의 첫 번째 날짜를 반환 |
firstInMonth | 현재 달의 첫 번째 요일 날짜를 반환 |
lastDayOfNextMonth | 현재 달의 마지막 날짜 반환 |
lastDayOfNextYear | 올해의 마지막 날짜를 반환 |
lastInMonth | 현재 달의 마지막 요일 날짜 반환 |
next previous | 현재 달에서 현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜 반환 |
nextOrSame | 현재 날짜 이후로 지정한 요일이 처음/이전으로 나타나는 날짜 반환 |
previousOrSame | 현재 날짜 포함 반환 |
DateTimeFormatter
특정 Formatter을 만들고 싶다면 DateTimeFormatterBuilder을 이용하면 된다.
13장 디폴트 메세지
인터페이스를 정의하는 두 가지 방법이 존재한다.
1. 인터페이스 내부에 있는 정적 메소드를 사용
2. 디폴트 메소드를 사용
그럼 default, 정적 메서드는 무엇일까?
아래의 코드에서 sort는 디폴트, naturalOrder()은 새로 추가된 정적 메소드다.
우리는 보통 인터페이스 그리고 해당 인터페이스를 활용할 수 있게 정의된 유틸리티 클래스를 주로 사용한다.
우리가 자주 사용하는 Collections는 Collection 객체를 활용할 수 있는 유틸리티 클래스이다.
보통 공개된 인터페이스에 추상 메소드를 추가하면 호환성이 깨지는 상황이 발생한다.
ERROR : AbstractMethodError : setRelativeSize()
디폴트 메세지 덕분에 라이브러리 설계자가 API를 바꿔도 기존 버전과 호환성을 유지할 수 있다.
클래스 또는 인터페이스로부터 같은 시그니처를 갖는 메소드를 상속 받을 시 주의할 점이 존재한다.
1. 클래스가 우선 순위가 가장 높다. 디폴트 메소드 보다 높은 우선 순위를 갖는다.
2. 1번외의 상황에서는 서브 인터페이스의 우선 순위가 높다. B가 A를 상속 받느면 B가 A보다 우선 순위가 높다.
3. 우선 순위가 정해지지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 어떤 디폴트 메세지를 호출할지 결정해야한다.
16장 안정적 비동기 프로그래밍
우선 동기 API와 비동기 API의 차이로 시작하자
동기 API는 메서드 호출 후 해당 메서드가 완료될 때까지 기다렸다가 메서드가 반환되면 그 다음 동작을 실행한다. 호출자와 피호출자가 다른 스레드에서 작업을 하고 있어도 호출자는 피호출자의 동작 완료를 기다려야한다. 이와 같은 상황을 블록 호출이라고 한다.
비동기 API는 메서드가 즉시 반환되며 끝내지 못한 작업은 호출자 스레드와 동기적으로 실행될 수 있도록 다른 스레드에 할당한다.
이런 상황을 비블록 호출이라고 한다. 다른 스레드에서 작업한 결과값을 콜백 메서드를 호출해서 전달하거나 호출자가 '계산 결과가 끝날때까지 기다린다'의 메서드를 호출하면서 전달 된다.
해당 장에서 중점을 보는 메서드는 CompletableFuture과 Future이다.
코드로 확인해보자
Future을 CompletableFuture로 변경하면
위와 같은 형식으로 변경할 수 있다. supplyAsync는 Supplier을 인수로 받아서 CompletableFuture을 돌려준다.
이 상황에서 CompletableFuture은 결과를 비동기적으로 산출해준다.
비블록 코드를 확인해보자.
직렬 동기로 코드를 처리하면 아래와 같은 시간이 소요 된다.
코드를 병렬처리 동기로 바꿔보자
단축이 가능하다. 그럼 CompletableFuture으로 코드를 수정해보자
위의 코드에 대해서 작은 설명을 첨부한다.
shop -> CompletableFuture.supplyAsync(
() -> shop.getName() + " price is " + shop.getPrice(product))
CompletableFutures로 각각의 가격을 비동기적으로 계산한다.
return priceFutures.stream().map(CompletableFuture::join)
return 구간에서 모든 비동기 동작이 마무리 되기를 기다린다. CompletableFurture은 기존 작업 요청이 완료 되어야 join이 결과를 반환하고 다음 상점 정보를 요청하는 코드다.
그럼 우리는 당연하게 의문점 하나가 발생한다. Stream을 왜 둘로 나눠서 실행한것인가?
한 스트림으로 처리하면 스트림의 게으른 특성으로 인해 비동기 처리가 아닌 동기 그리고 순차적으로 코드가 진행되어버린다.
결과에 큰 변화는 없다.
그럼 조금 더 개선을 해보자. 4개의 스레드로 4개의 작업을 한번에 병렬로 처리한다면?
우리는 커스텀 Executor을 사용해볼것이다.
너무 만은 스레드는 서버에 무리를 준다. 사용할 최대 스레드는 100개 이하로 설정하자.
... (중간의 Executor은 다시 보는 것으로...)
Future을 사용할때의 주의점은 무한정 기다리는 상화잉 발생할 수 있는 점이다. 이런 상황에서는 Completablefuture에서 제공해주는
.orTimeout 메서드를 이용하자
17장 리액티브 프로그래밍
리액티브 프로그래밍은 다양한 시스템과 소스에서 들어오는 데이터 항목 스트림을 비동기적으로 처리하고 합쳐서 이런 문제를 해결한다.
위와 같은 처리 방식으로 사용자에게 높은 응답성, 애플리케이션 소장, 정전 같은 상태에 대한 대처, 그리고 다양한 네트워크 상태에서 메세지를 교환하고 전달할 수 있으며 무거운 작업을 하는 상황에서도 가용성을 제공한다.
리액티브 프로그래밍의 핵심 원칙은
반응성 : 일정하고 예상 가능한 반응시간을 제공
회복성 : 장애가 발생하더라도 시스템은 반응해야한다.
탄력성 : 무거운 작업 부하가 발생하면 자동으로 관련 컴포넌트에 할당된 자원수를 늘린다.
메세지 주도 : 회복성과 탄력성을 지원하기 위해서는 약한 결합, 고립, 위치 투명성 등을 지원할 수 있게 컴포넌트의 경계를 명확하게 정의해야한다. 컴포넌트는 비동기 메세지 전달해 컴포넌트끼리의 통신이 이뤄진다. 이 덕분에 회복성, 탄력성을 얻을 수 있다.
우리가 제일 중점으로 볼것은 비동기로 작업을 수행한다는 점이다.
애플리케이션 수준 리액티브 프로그래밍
시스템 수준의 리액티브
리액티브 스트림과 플로 API
Flow
리액티브 프로젝트의 표준에 따라 프로그래밍 발행 - 구독 모델을 지원할 수 있다.
- Publisher
- Subscriber
- Subscription
- Processor
Publisher이 항목을 발행하면 Subscriber은 한 개씩 또는 한 번에 여러 항목을 소비한다. 이때 Subscription이 이 과정을 관리할 수 있도록 Flow 클래스는 관련된 인터페이스와 정적 메서드를 제공하고 있다. Publisher은 수많은 이벤트를 발행할 수 있지만 subscriber의 요구사하에 따라 역압력 기법으로 인해 이벤트 제공 속도가 제한된다.
Publisher을 살짝 살펴보자
Subscriber
Subscription에서는 항상 onSubscribe가 첫번째로 호출 된다. 그 이후 여러 onNext가 여러 번 호출 될수 있다.
이벤트 스트림은 지속되거나 onComplete 콜백을 통해서 더이상 데이터가 없고 종료 되었다는것을 알 수 있으며 Publisher에서 에러가 발생하면 onError를 호출한다.
onSubscribe 안의 Subscription을 살펴보자
Subscription
이들 인터페이스 구현이 어떻게 서로 협력해야 하는지의 규칙은 아래를 살펴보자
- Publisher은 반드시 Subscription의 request 메서드에 정의된 개수 이하의 요소만 Subscriber에 전달해야 한다.
- Subscriber는 요소를 받아 처리할 수 있음을 Publisher에 알려야 한다. 이 방법으로 Subscriber는 Publisher에 역압력을 행사할 수 있고 Subscriber가 관리할 수 없는 양을 받는 일을 피할 수 있다. 그리고 마지막까지 Subscription.cancel()이 호출된 이후라고 한 개 이상의 onNext를 받을 준비가 되어있어야한다.
- Publisher과 Subscriber는 정확하게 Subscription을 공유해야하며 각각의 역할을 수행해야 한다. 이를 위해서는 onSubscribe, onNext 메서드에서 Subscriber는 request 메서드를 동기적으로 호출할 수 있어야 한다. 규칙에서는 Subscription.cancel() 메서드는 몇 번을 호출해도 한 번 호출하는것과 같은 효과를 가져야한다. 하나의 호출이 다른 호출에 영향을 주지 않도록 스레드 세이프여야한다.
RxJava
+) 사용 방법은 아래 깃을 참고하자
https://github.com/ReactiveX/RxJava
Observable에는 just메서드, interval메서드가 존재한다
just() : 메서드는 한개 이상의 요소를 이용해 이를 방출하는 Observable로 변환한다.
아래 코드는 onNext("first") > onNext("second") > onComplete 로 진행된다.
Observable<String> strings = Observable.just("first", "second");
interval() : interval은 onePerSec의 변수로 Observable을 반환해 할당한다.
Long 1의 값을 무한으로 증가 시키며 값을 방출한다.
Observable<Long> onePerSec = Observable.interval(1, TimeUnit.SECONDS);
RxJava에서 Observable이 플로 API의 Publisher 역할을 하며 Observer는 Flow의 Subscriber 인터페이스 역할을 한다.
Observable은 역압력을 지원하지 않으므로 Subscription의 request메서드를 포함하지 않는다.
Observer을 살펴보자
Observable, Emitter을 살펴보자
RxJava는 플로 API보다 유연한 편이다.
RxJava, 리액티브 리이브러리는 스트림을 합치고 만들고 다양한 방법을 제공한다.
filter, map ...
깃 주소 : https://github.com/EssLemint/modernjavainaction
'개발 > JAVA' 카테고리의 다른 글
DTO에서 @Setter쓰는 것에 대한 주저리[1] (0) | 2023.09.17 |
---|---|
자바 flatMap 이해하기 (0) | 2022.11.03 |
@NoArgsConstructor(access = PROTECTED)에 관하여 (0) | 2022.09.12 |
ModelMapper와 친해지기 (0) | 2022.07.14 |
Iterator (0) | 2022.07.11 |