Java 의 Stream API 는 컬렉션 데이터를 효율적으로 처리하기 위한 도구로 이때 자주 사용되는 map 과 flatMap 메소드는 각각 변환과 스트림 평면화를 담당합니다. 비슷하게 Optional 클래스도 map 과 flatMap 메소드를 제공하는데 Optional 은 null 처리를 깔끔하게 하기 위한 도구로 map 과 flatMap 을 사용하면 더 안전하고 직관적으로 데이터를 다룰 수 있습니다.
Stream
map()
map 은 Stream API 가 제공하는 중간 연산 메소드로 각각의 엘리멘트에 접근하여 특정 작업을 수행하고 그 결과를 새로운 스트림으로 반환합니다.
데이터를 변환하고자 할 때 매우 유용하게 사용됩니다. 아래 예제를 통해 Member 객체 리스트에서 이름만 추출해 리스트로 변환하는 과정을 살펴보겠습니다.
먼저 사용할 Member와 Team 클래스를 정의합니다. 여기서 Member는 이름과 팀 정보를 가지고 있으며 이 객체 리스트에서 이름을 추출할 것입니다.
class Member {
private String name;
private Team team;
public Member(String name) {
this.name = name;
}
public Member(String name, Team team) {
this.name = name;
this.team = team;
}
}
class Team {
private String name;
}
이제 Stream 을 활용하여 각 Member 객체에서 name 필드만 추출하는 과정을 보여주는 테스트 코드를 살펴보겠습니다. map 메소드를 통해 각 Member 객체의 이름을 리스트로 변환해보겠습니다.
@Test
@DisplayName("test stream map method")
void testMap() throws Exception {
// given
List<Member> members = List.of(
new Member("Alice"),
new Member("Bob"),
new Member("Charlie"),
new Member("Dave")
);
// when
List<String> names = members.stream()
.map(member -> member.name)
.toList();
// then
assertThat(names).containsExactly("Alice", "Bob", "Charlie", "Dave");
}
flatMap()
flatMap 도 Stream API 에서 제공하는 중간 연산 메소드로 각각의 엘리먼트에 대해 매핑된 여러 개의 스트림을 하나의 스트림으로 평탄화하는 역할을 합니다. map 메소드와는 다르게 중첩된 스트림 제거하고 하나의 스트림으로 즉 입력한 원소를 가장 작은 단위의 단일 스트림으로 반환합니다. 이번 예제에서는 Member 객체의 이름을 한 글자씩 나누고 그 중 특정 글자가 포함된 값을 찾는 과정을 살펴보겠습니다.
아래 테스트 코드는 flatMap을 활용해 멤버 이름을 개별 문자로 나눈 뒤 특정 조건을 만족하는 문자가 있는지 확인하는 과정입니다.
@Test
@DisplayName("test stream flatMap method")
void testFlatMap() throws Exception {
// given
List<Member> members = List.of(
new Member("aaa"),
new Member("bbb"),
new Member("ccc"),
new Member("dd1")
);
// when
Optional<String> any = members.stream()
.flatMap(member -> Arrays.stream(member.name.split("")))
.filter(name -> name.contains("1"))
.findAny();
// then
assertThat(any).isPresent();
}
Optional
Optional API와 Stream API 간에는 직접적인 구현이나 상속 관계가 없지만 사용 방법과 기본 사상이 매우 유사합니다. 두 API 모두 데이터를 처리하는 중간 연산자로 map(), flatMap(), filter()와 같은 메소드를 제공하지만 그 사용법은 조금 다릅니다. 이번 섹션에서는 Optional 의 map 과 flatMap 메소드에 대해 살펴보겠습니다.
map()
Optional 의 map 메소드는 내부에 값이 존재할 때만 동작하며 해당 값을 변환하는 식을 적용한 후 그 결과를 담은 새로운 Optional 을 반환합니다. 만약 내부 값이 존재하지 않으면 변환 과정은 생략되고 빈 Optional 이 반환됩니다. Optional의 map 메소드는 null-safe한 연산에 주로 사용되며 값이 있을 수도 없을 수도 있는 상황에서 단일 값을 안전하게 변환하는 데 매우 유용합니다.
아래 테스트 코드는 Optional 의 map 메소드를 활용하여 Member 객체에서 Team 필드를 안전하게 추출하고 그 안의 name 필드에 접근하는 과정입니다.
@Test
@DisplayName("test optional map method")
void testOptionalMap() throws Exception {
// given
Member alice = new Member("Alice");
// when
Optional<String> teamName = Optional.of(alice)
.map(member -> member.team)
.map(team -> team.name);
// then
assertThatThrownBy(() -> teamName.orElseThrow(RuntimeException::new))
.isInstanceOf(RuntimeException.class);
}
flatMap()
Stream API 의 flatMap 과 동일하게 복잡한 객체 관계나 여러 단계의 값 추출에서 발생할 수 있는 중첩을 제거하고 코드의 가독성과 안정성을 높일 수 있습니다.
@Test
@DisplayName("test optional flatMap method")
void testOptionalFlatMap() throws Exception {
// given
Member alice = new Member("Alice", new Team("TeamA"));
// when
Optional<Team> team1 = Optional.of(alice)
.flatMap(member -> Optional.ofNullable(member.team));
Optional<Optional<Team>> team2 = Optional.of(alice)
.map(member -> Optional.ofNullable(member.team));
// then
assertThat(team1.orElseThrow()).isInstanceOf(Team.class);
assertThat(team2.orElseThrow()).isInstanceOf(Optional.class);
}
반환 타입으로 확인할 수 있듯이 flatMap 은 현재 다루고 있는 원소를 가장 작은 단위의 단일 Optional 로 반환합니다.
오탈자 및 오류 내용을 댓글 또는 메일로 알려주시면, 검토 후 조치하겠습니다.
'Language > Java' 카테고리의 다른 글
[Java] Thread Pool (0) | 2024.07.18 |
---|---|
[Java] Thread (1) | 2024.06.30 |
[Java] Native Method (0) | 2024.06.15 |
[Java] 동기화 (0) | 2024.03.13 |
[Java] 데이터 병렬 처리(Java 8) (0) | 2024.01.08 |