개발자 노트

stream api - peek 본문

컴퓨터 언어/Java

stream api - peek

jurogrammer 2022. 12. 13. 01:11

Stream API

peek?

문서에서 왜 Stream.peek을 주로 디버깅 용으로 사용하라고 했는가? 대하여 알아보겠습니다.

Stream API

peek은 Stream api의 한 부분이므로 Stream API 문서부터 살펴보도록 하겠습니다.

stream api의 문서를 보시면 filter, map등과 같은 method에 전달되는 함수는 올바른 동작을 위하여 다음 2가지 제약조건을 만족해야 한다고 합니다.

  • non-interfering이여야 할 것
  • 대부분의 경우, 상태를 지니지 말아야 할 것

여기서, non-interfering에 주목해보겠습니다

non-interfering

interfering의 사전적 의미

간섭하는, 참견하기 좋아하는

문서에서 말하는 non-interfering

stream pipeline이 실행되는 동안 data source를 조금도 수정하지 않아야 하는 것을 의미한다. - 문서 참조

이를 어길 경우

  • 의도치 않은 결과가 발생하거나
  • exception이 발생할 수 있다고 합니다.

위 지식을 바탕으로 java docs에 설명된 peek을 살펴보겠습니다.

Peek

문서 링크

여기서 2가지를 참조하시면 됩니다.

  • peek에 전달되는 action parameter는 non-interfering action이어야 한다.
  • peek은 intermediate operation이다.

다시 말해서 peek 연산에서 source data를 변경시키면 안 된다는 뜻입니다.

intermediate operation가 왜 중요한 지는 앞으로 예시를 보며 살펴보겠습니다.

예시

실제로 source data를 수정하는 지 그리고 peek의 의도치 않은 결과를 같이 살펴보겠습니다.

source data의 수정

코드

@Test
void test() {
    List<Wrapper> origin = List.of(new Wrapper(1L), new Wrapper(2L));

    List<Wrapper> modified = origin.stream()
        .peek(h -> h.setA(4L))
        .collect(Collectors.toList());

    assertThat(origin).isNotEqualTo(modified);
}

public static class Wrapper {
    private Long a;

    public void setA(Long a) {this.a = a;}

    public Wrapper(Long a) {this.a = a;}

    @Override
    public String toString() {
        return "Wrapper{" +
            "a=" + a +
            '}';
    }
}

결과

data source에 해당하는 origin list의 원소 값이 변경되었습니다.

peek과 count - 1

코드

아래 코드는 콘솔에 무엇이라 출력될까요?

@Test
void test2() {
    long count = IntStream.range(1, 10)
        .peek(System.out::println)
        .count();

    assertThat(count).isEqualTo(9);
}

결과

출력이 없습니다. java 9에서 terminal operation인 count의 최적화 때문입니다.

peek은 count의 갯수에 영향을 주지 않으므로 표준출력하는 함수가 실행되지 않았습니다.

즉, intermediate operation은 최적화를 위해 실행되지 않을 수 있습니다.(lazy evaluation 참조)

peek과 count - 2

코드

@Test
void test3() {
    long count = IntStream.range(1, 10)
        .filter(n -> n % 2 == 0)
        .peek(System.out::println)
        .count();

    assertThat(count).isEqualTo(4);
}

결과

filter는 count의 결과에 영향을 주는 intermediate operation이므로 이때 peek은 평가가 됩니다.

주의

따라서 개발을 할 때 peek 사용시 주의할 점을 정리하자면 다음과 같습니다.

  • peek은 intermediate operation이기 때문에 terminal operation에 따라 평가되지 않을 수 있음. (lazy evaluation으로 최적화)
  • document의 설명과 다르게 사용하므로 추후 버전 업 시 문제될 수 있음.
    • ex:) java 8 → java 9에서 count()의 최적화

마지막으로 서로 다른 패러다임을 지닌 메서드를 살펴보고 마무리를 짓겠습니다.

Stream.forEach와 Iterable.forEach

동일한 메서드 명이지만, 함수형 패러다임으로 만들어진 Stream.forEach 그리고,

절차지향 패러다임으로 만들어진 Iterable.forEach가 어떻게 설명이 다른 지 살펴보겠습니다.

Stream.forEach 도 동일하게 non-interfering action을 전달하라고 명시되어 있는 반면, - 링크

Iterable.forEach는 source element를 수정하면 side-effect가 발생할 수 있다고 주의만 작성되어 있습니다. - 링크

고민 - 표현

peek과 forEach는 반드시 side-effect를 주어 의도치 않은 결과 발생할 수 있음

  • 표준 출력조차 사이드 이펙트로 정의. side-effect 중 data source를 수정하지 않는 non-interfering action을 구현해야 한다는 뜻으로 해석 가능.
반응형

'컴퓨터 언어 > Java' 카테고리의 다른 글

Collection 정리 - Set Interface  (2) 2022.07.14
Collection 정리 - Collection Interface  (0) 2022.07.06
Collection 정리 - Implementations  (0) 2022.07.04
Collections정리 - Interfaces  (0) 2022.06.30
Collection 정리  (0) 2022.06.30
Comments