티스토리 뷰
개념
-
스트림은 파라미터로 제공되는 함수(
Function<T, Stream<R>>
과 관련된 함수형 인터페이스)를 적용해서 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행하는flatMap
이라는 메서드를 제공한다. -
추상적으로 설명하면 차원을 낮추는 개념이라고 생각된다. (ex.
Stream<String[]>
->Stream<String>
)- 위의 예시의 경우 일반
map
메서드를 사용할 경우String[]
배열 자체가 다른 객체로 변환되는데flatMap
을 사용하면String[]
내부의 값들 자체를 다른 객체로 변환하는 것이 가능하다.
- 위의 예시의 경우 일반
-
여기서 말하는 함수는
map
의Function<T, R>
과 약간의 차이가 있다.flatMap
에서 사용되는Function
은 T는 동일하지만 R이Stream<R>
이다.- 즉, 일반 제네릭 타입(R)의 객체가 아니라 일반 스트림(
Stream<R>
)이나 기본형 특화 스트림(IntStream
,LongStream
,DoubleStream
)과 같이 스트림 타입이 반환되어야 한다.
-
flatMap
의 기본 개념은 위의 이미지와 같다. -
또한
flatMap
은 반환하는 형태에 따라 여러 메서드가 존재한다.flatMapToInt
,flatMapToLong
,flatMapToDouble
이 다음에 해당한다.- 일반 스트림에서만 위의 3가지 메서드가 추가로 존재하며 기본형 특화 스트림에서는
flatMap
만 존재하고 각각의 타입에 맞는 기본형 특화 스트림을 반환한다.
flatMap 메서드
- 위에서 설명한 가장 기본적인 형태의
flatMap
메서드다. - 일반 스트림과 기본형 특화 스트림 모두에서 제공하는 메서드다.
- 스트림 별로
flatMap
의 파라미터를 살펴보면 다음과 같다.
Stream의 flatMap
메서드
IntStream의 flatMap
메서드
LongStream의 flatMap
메서드
DoubleStream의 flatMap
메서드
- 스트림의 종류에 따라 각 메서드의 파라미터로
Function<T, Stream<R>>
,IntFunction<IntStream>>
,LongFunction<LongStream>>
,DoubleFunction<DoubleStream>>
을 전달받는다.
flatMapToInt 메서드
- 일반 스트림을
IntStream
으로 변환해주는 메서드다. - 일반 스트림에서만 제공하는 메서드다.
- 위의 이미지처럼 일반 스트림으로 변환하는
flatMap
과 다르게Function<? super T,? extends IntStream> mapper
를 파라미터로 전달한다.
flatMapToLong 메서드
- 일반 스트림을
LongStream
으로 변환해주는 메서드다. - 일반 스트림에서만 제공하는 메서드다.
- 위의 이미지처럼 일반 스트림으로 변환하는
flatMap
과 다르게Function<? super T,? extends LongStream> mapper
를 파라미터로 전달한다.
flatMapToDouble 메서드
- 일반 스트림을
DoubleStream
으로 변환해주는 메서드다. - 일반 스트림에서만 제공하는 메서드다.
- 위의 이미지처럼 일반 스트림으로 변환하는
flatMap
과 다르게Function<? super T,? extends DoubleStream> mapper
를 파라미터로 전달한다.
예제
-
아래 예제에서 사용되는 내용들은 다양한 상황을 연출하기 위해서 임의로 만들어진 내용이므로 특정 속성에 대해서 왜 저 타입이 사용되었는지 의문을 가지지 말자!
-
예제 코드에서 사용되고 있는 스펙
- Java 15 preview (record라는 새로운 클래스 개념을 사용하기 위해서 해당 프리뷰 버전을 사용. 하위 버전의 경우는 일반 클래스를 생성한 후 getter를 만들고 사용하면 됨) - 참고
- JUnit 5
- Gradle 6.7
기본 데이터 생성
public record AccessLog(Long no, String ip, LocalDate accessedAt, long responseTime) {
}
public record User(Long no, String id, List<AccessLog> accessLogs) {
}
private final FlatMapUsage flatMapUsage = new FlatMapUsage();
private final List<User> users = List.of(
new User(1L, "alpha", List.of(
new AccessLog(1L, "192.168.0.1", LocalDate.of(2015, 11, 30), 10),
new AccessLog(3L, "192.168.0.2", LocalDate.of(2018, 3, 3), 20),
new AccessLog(7L, "192.168.0.3", LocalDate.of(2020, 7, 15), 100))),
new User(2L, "beta", List.of(
new AccessLog(2L, "192.168.0.4", LocalDate.of(2018, 3, 3), 25),
new AccessLog(4L, "192.168.0.5", LocalDate.of(2019, 8, 23), 17),
new AccessLog(6L, "192.168.0.6", LocalDate.of(2020, 5, 1), 80))),
new User(3L, "gamma", List.of(
new AccessLog(5L, "192.168.0.7", LocalDate.of(2020, 2, 25), 150),
new AccessLog(8L, "192.168.0.8", LocalDate.of(2020, 8, 5), 200),
new AccessLog(9L, "192.168.0.9", LocalDate.of(2021, 1, 5), 55))));
문자열 리스트를 한 글자씩 분리해서 고유 문자들만 반환
- 주어진 문자열 리스트에서 고유 문자만 추출해서 반환해보자.
public List<String> getAllDistinctStrings(List<String> strings) {
return strings.stream()
.map(s -> s.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
}
-
map
메서드가Stream<String[]>
을 반환하고flatMap
내부에서Arrays::stream
을 사용해서String[]
을Stream<String>
으로 변환함과 동시에Stream<Stream<String>>
을Stream<String>
으로 평면화시킨다.- 여기서
flatMap
이 아닌map
을 사용할 경우Stream<String>
이 아니라Stream<Stream<String>>
로 변환된다.
- 여기서
@Test
@DisplayName("문자열 리스트를 한글자씩 분리해서 고유 문자들만 반환")
void getAllDistinctStringsTest() {
final var strings = List.of("Happy", "New", "Year");
final var expected = List.of("H", "a", "p", "y", "N", "e", "w", "Y", "r");
final var result = flatMapUsage.getAllDistinctStrings(strings);
assertIterableEquals(expected, result);
}
- 다음과 같은 과정으로 테스트가 성공한다.
특정 날짜 이후에 접속한 유저의 ip 조회
- 실무에서 사용할 법한 예시를 생각해보자.
public List<String> getIpByAccessedAtAfter(List<User> users, LocalDate accessedAt) {
return users.stream()
.map(User::accessLogs)
.flatMap(l -> l.stream()
.filter(a -> a.accessedAt().isAfter(accessedAt))
.map(AccessLog::ip))
.collect(Collectors.toList());
}
- 유저 정보와 접속 기록 정보를 데이터베이스나 파일에서 가져왔다고 하고 특정 날짜 이후에 접속한 유저의 ip 목록을 조회해보자.
map
메서드가Stream<List<AccessLog>>
을 반환하고flatMap
내부에서List<AccessLog>
를map
을 사용해서Stream<String>
으로 변환함과 동시에Stream<Stream<String>>
을Stream<String>
으로 평면화시킨다.
@Test
@DisplayName("특정 날짜 이후에 접속한 유저의 ip 조회")
void getIpByAccessedAtAfterTest() {
final var accessedAt = LocalDate.of(2020, 1, 1);
final var expected = List.of("192.168.0.3", "192.168.0.6", "192.168.0.7", "192.168.0.8", "192.168.0.9");
final var result = flatMapUsage.getIpByAccessedAtAfter(users, accessedAt);
assertIterableEquals(expected, result);
}
- 다음과 같은 과정으로 테스트가 성공한다.
모든 유저의 평균 응답 시간 구하기
- 이번에는 기본형 특화 스트림을 파라미터로 가지는
flatMap
관련 메서드를 사용해보자.
public OptionalDouble getAverageResponseTime(List<User> users) {
return users.stream()
.map(User::accessLogs)
.flatMapToLong(l -> l.stream()
.mapToLong(AccessLog::responseTime))
.average();
}
- 유저 정보와 접속 기록 정보를 데이터베이스나 파일에서 가져왔다고 하고 모든 유저들의 응답 시간 평균을 구해보자.
map
메서드가Stream<List<AccessLog>>
을 반환하고flatMapToLong
내부에서List<AccessLog>
를mapToLong
을 사용해서 기본형 특화 스트림인LongStream
으로 변환함과 동시에Stream<LongStream>>
을LongStream
으로 평면화시킨다.
@Test
@DisplayName("모든 유저의 평균 응답 시간 구하기")
void getAverageResponseTimeTest() {
final var expected = 73.0;
final var result = flatMapUsage.getAverageResponseTime(users);
assertTrue(result.isPresent());
assertEquals(expected, result.getAsDouble());
}
- 다음과 같은 과정으로 테스트가 성공한다.
이상 Stream API에서 사용하는 중간 연산 중 처음에 이해하기 어려운 flatMap
관련 메서드에 대해 살펴보았다.
'프로그래밍 > Java' 카테고리의 다른 글
[Stream API] 중간 연산 - sorted 메서드 (0) | 2021.01.07 |
---|---|
[Stream API] 중간 연산 - peek 메서드 (0) | 2021.01.07 |
[Stream API] 중간 연산 - map 메서드 (2) | 2021.01.04 |
[Stream API] 중간 연산 - slicing 관련 메서드들 (0) | 2021.01.03 |
[Stream API] 중간 연산 - distinct 메서드 (0) | 2020.12.31 |
- Total
- Today
- Yesterday
- lambda
- #React #ReactJS #리액트
- flatMapToLong
- import문
- 스트림
- Java8
- modern java
- 람다
- mapToObj
- 자바
- jdk14
- 익명 클래스
- flatMapToDouble
- #예제 #example #가계부 #Account Book
- flatMapToInt
- 중간 연산
- 변경사항
- IntelliJ
- flaMap
- 다짐
- 회고
- java
- 충북 콕! 콕!
- #배열 #array #map 함수
- java14
- 개발자
- 목표
- Stream API
- 토이 프로젝트
- 계획
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |