본문 바로가기
Language/Java

[Java] Stream 과 Optional 의 map, flatMap

by 기몬식 2024. 9. 6.

출처


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