Logo

Java9에서 강화된 Stream 클래스의 iterate 정적 메서드

Java9에서는 기존 Java8 때 부터 있었던 Stream 클래스의 iterate() 메서드를 오버로드(overload)한 신규 메서드가 추가되었습니다. 이번 포스팅에서는 Stream 클래스의 정적 메서드인 iterate()에 대해서 얘기해보고자 합니다.

메서드 이름이 암시하듯 iterate()는 어떤 연산을 반복적으로 수행할 때 사용되는 메서드이며, 명령형 프로그래밍(imperative programming)에서 forwhile과 같은 루프문으로 해결하던 코드를 함수형 프로그래밍(functional programming) 방식으로 작성할 때 유용하게 사용할 수 있습니다.

이게 도대체 무슨 말인지 간단하게 0과 9 사이의 홀수를 더하는 예제 코드(1 + 3 + 5 + 7 + 9 = 25)를 통해서 살펴보겠습니다.

for 루프

먼저 for 루프를 사용하여 0과 9 사이의 모든 홀수를 더하는 더하는 전형적인 코드를 살펴보겠습니다.

int sum = 0;
for (int i = 0; i < 10; i++) {
  if (i % 2 == 1) {
      sum += i;
  }
}
System.out.println("합: " + sum); // 합: 25

이 코드를 읽어보면, “일단 sum 변수를 0으로 초기화 후, i 변수를 0부터 10보다 작다면 1씩 계속 증가시키되, 만약 i가 홀수이면 sum 값에 더한다.” 처럼 될 것입니다. 명령형 프로그래밍에서는 이처럼 지속적으로 변수의 상태를 변경하면서 수행해야 하는 단계를 매우 자세히 설명합니다.

Java8의 iterate()

Java8에서는 Stream 클래스의 iterate() 메서드는 2개의 인자만 받았습니다. 첫번째 인자는 초기 값이고, 두번째 인자는 값이 어떻게 변경될지를 나타내는 람다 함수입니다.

int sum = Stream.iterate(0, i -> i + 1)
  .filter(i -> i % 2 == 1)
  .limit(5)
  .mapToInt(Integer::intValue)
  .sum();
System.out.println("합: " + sum); // 합: 25

이 코드를 읽어보면, “초기 값 0에서 1씩 증가되는 값들을 담고 있는 스트림에서 홀수값을 처음 5개만 남기고 언박싱(unboxing) 후 더해서 sum 변수에 할당하라.” 처럼 될 것입니다. 이 코드에서 limit()를 통해 스트림을 끊는 부분 상당히 눈에 거슬리지만, 이 부분이 없으면 이 코드는 멈추지 않고 영원히 돌아가게 됩니다.

이처럼 Java8에서 iterate() 메서드는 조심스럽게 사용하지 않으면 무한 스트림을 만들어낼 수 있기 때문에 개발자들에게 많이 사랑 받지는 못했던 것 같습니다.

Java9의 iterate()

Java9에서는 추가로 스트림 종료 조건을 인자로 받는 iterate() 메서드가 Stream 클래스에 추가되었습니다. 첫번째 인자는 초기 값이고, 두번째 인자는 종료 조건을 나타내는 람다 함수, 세번째 인자는 값이 어떻게 변경될지를 나타내는 람다 함수입니다.

int sum = Stream.iterate(0, i -> i < 10, i -> i + 1)
  .filter(i -> i % 2 == 1)
  .mapToInt(Integer::intValue)
  .sum();
System.out.println("합: " + sum); // 합: 25

이 코드를 읽어보면, “초기 값 0에서 10보다 작다면 1씩 증가되는 값들을 담고 있는 스트림에서 홀수값만 남기고 언박싱(unboxing) 후 더해서 sum 변수에 할당하라.” 처럼 될 것입니다.

이처럼 Java9에서는 iterate() 메서드의 사용성이 개선되었기 때문에 예전보다는 좀 더 많이 사용되지 않을까 기대해봅니다. 특히, 기존에 for 루프문과 문법이 상당히 유사하기 때문에, 함수형 프로그래밍 방식의 코드를 선호하시는 분들께는 iterate() 메서드가 크게 어필할 수 있을 것 같습니다.

참고