티스토리 뷰

개념

  • 스트림에는 중간 연산의 수행 결과를 디버깅할 수 있는 수단인 peek 메서드를 제공한다.

  • 파라미터로 주어지는 Consumer<? super T> action를 사용해서 스트림의 요소들을 소모한다.

    • Consumer<T>를 받는 만큼 스트림의 요소들을 가지고 추가적은 작업을 수행할 수도 있다.
  • peek 메서드와 forEach 메서드를 혼동해서는 안된다.

    • peek 메서드와 forEach 메서드 모두 System.out::println 같은 Consumer<T>를 파라미터로 받는다.
    • forEach 메서드는 최종 연산이기 때문에 결과를 확인할 수 있으나 peek 메서드는 중간 연산이기 때문에 어떠한 최종 연산도 하지 않으면 아무것도 확인할 수가 없다.
  • 보통 잘 사용되지 않는 메서드지만 알아둘 필요는 있다고 생각한다.

예제

  • 아래 예제에서 사용되는 내용들은 다양한 상황을 연출하기 위해서 임의로 만들어진 내용이므로 특정 속성에 대해서 왜 저 타입이 사용되었는지 의문을 가지지 말자!

  • 예제 코드에서 사용되고 있는 스펙

    1. JUnit 5
    2. Gradle 6.7
  • 소스 코드

기본 데이터 생성

public class Address {
    private final int no;
    private final String si;
    private final String gu;
    private final String dong;
    private String fullName;

    public Address(int no, String si, String gu, String dong, String fullName) {
        this.no = no;
        this.si = si;
        this.gu = gu;
        this.dong = dong;
        this.fullName = fullName;
    }

    public String getSi() {
        return si;
    }

    public String getGu() {
        return gu;
    }

    public String getDong() {
        return dong;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    @Override
    public boolean equals(Object obj) {
        final var address = (Address) obj;
        return no == address.no && si.equals(address.getSi())
                && gu.equals(address.getGu()) && dong.equals(address.getDong())
                && ((fullName == null && address.getFullName() == null) || fullName.equals(address.getFullName()));
    }

    @Override
    public String toString() {
        return "Address{" +
                "no=" + no +
                ", si='" + si + '\'' +
                ", gu='" + gu + '\'' +
                ", dong='" + dong + '\'' +
                ", fullName='" + fullName + '\'' +
                '}';
    }
}
  • 현재 Java의 단점이 부각되는 코드인 것 같다. 데이터 클래스를 하나 만드는데 너무 많은 내용이 필요하다.
private final PeekUsage peekUsage = new PeekUsage();
private final List<Address> addresses = List.of(
        new Address(1, "서울시", "관악구", "신림동", null),
        new Address(2, "서울시", "강남구", "논현동", null),
        new Address(3, "서울시", "동작구", "사당동", null),
        new Address(4, "서울시", "서초구", "양재동", null));

두 수의 공배수 구하기

  • 주어진 두 수와 최댓값 n이 주어질 때 두 수의 공배수를 구해보자.
public List<Integer> getCommonMultipleAAndBUntilN(int a, int b, int n) {
    return IntStream.rangeClosed(1, n)
            .filter(i -> i % a == 0)
            .peek(i -> System.out.println(a + "의 배수: " + i))
            .filter(i -> i % b == 0)
            .peek(i -> System.out.println(a + "와 " + b + "의 공배수: " + i))
            .boxed()
            .collect(Collectors.toList());
}
  • IntStream.rangedClosed(1, n)을 이용해서 1부터 n까지의 자연수를 생성하는 스트림을 만들었다.

  • 두 번의 filter 메서드를 통해서 배수 판별을 하고 중간중간에 peek 메서드를 사용해서 디버깅을 하고 있다.

  • collect 메서드를 호출하기 전에 boxed 메서드를 사용해서 기본형 특화 스트림의 primitive 타입을 Wrapper 클래스 타입으로 박싱한다.

    • 박싱을 하지 않으면 collect(Collectors.toList())를 사용해서 List<Integer>로 모을 수 없다.
    • 여기서 사용되는 boxed라는 메서드 또한 중간 연산 중 하나지만 내용 자체가 기본형 특화 스트림을 그에 해당하는 Wrapper 클래스 타입의 스트림으로 박싱하는 역할만 하기 때문에 별도로 포스팅하지는 않을 것이다.
    • IntStreamStream<Integer>, LongStreamStream<Long> 그리고 DoubleStreamStream<Double>로 변환시킨다.
  • 15 이하의 자연수 중에서 2와 3의 공배수를 구해보자.

@Test
@DisplayName("두 수의 공배수 구하기")
void getCommonMultipleAAndBUntilNTest() {
    final var expected = List.of(6, 12);
    final var result = peekUsage.getCommonMultipleAAndBUntilN(2, 3, 15);
    assertEquals(expected, result);
}
  • 다음과 같은 내용이 콘솔에 출력되고 테스트가 성공한다.

주소 풀네임 조합하기

  • 시, 구, 동 데이터가 있을 때 이것을 합쳐서 전체 주소로 만들어보자.
public List<Address> makeFullNameInAddress(List<Address> addresses) {
    return addresses.stream()
            .peek(a -> a.setFullName(a.getSi() + " " + a.getGu() + " " + a.getDong()))
            .collect(Collectors.toList());
}
  • 여기서는 peek 메서드를 사용해서 시, 구, 동을 하나의 문자열로 합치는 작업을 수행했다.

    • 개인적으로 이런 방식을 선호하지는 않는다.
    • 객체 내부의 값을 변경하는 방식 자체는 부수 효과를 유발할 수 있고 동기화 문제에 대해서도 별도의 처리가 필요하기 때문이다.
    • map을 통해 매번 새로운 객체를 생성하는 방법으로 유사한 동작을 할 수 있다.
    • 하지만 실제로 코드를 짜다보면 객체를 새로 생성하는 것에 대한 비용이 크다면 peek 메서드 사용을 고려해 볼 수는 있다.
    • 상황에 맞는 적절한 방식을 찾는 것이 중요한 것 같다.
@Test
@DisplayName("주소 풀네임 조합하기")
void getEvenNumbersCountTest() {
    final var expected = List.of(
            new Address(1, "서울시", "관악구", "신림동", "서울시 관악구 신림동"),
            new Address(2, "서울시", "강남구", "논현동", "서울시 강남구 논현동"),
            new Address(3, "서울시", "동작구", "사당동", "서울시 동작구 사당동"),
            new Address(4, "서울시", "서초구", "양재동", "서울시 서초구 양재동"));
    final var result = peekUsage.makeFullNameInAddress(addresses);
    result.forEach(System.out::println);
    assertIterableEquals(expected, result);
}
  • 다음과 같은 내용이 콘솔에 출력되고 테스트가 성공한다.

이상 Stream API에서 사용하는 중간 연산 중 peek 메서드에 대해 살펴보았다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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 29 30 31
글 보관함