클린 코드를 스터디 하면서 제가 이전에 짠 코드들을 리팩토링 하고 있는데요. 제가 출제한 문제인 22232번은 코딩 테스트에 꽤 나오는 유형입니다만, 제가 코드를 깔끔하게 짜지 않은 탓에 스터디 재료가 되고 말았습니다. 스터디를 하다가 스트림과 람다에 대해서 오갔는데요.

 

 다른 분의 코드를 보다가, 입력 부분을 Intstream과 range 등을 이용해서 깔끔하게 처리할 수 있다는 것을 보고 놀랐습니다. 사실 이전에도, 한 분이 기여를 해 주셔서 스트림과 람다의 존재는 알고 있었지만, 신세계였습니다. 심지어, String을 Integer로 변환하는 작업도 Stream을 잘 이용하면 잡스러운 로직들을 간결하게 짤 수 있다는 걸 보고 너무 놀랐습니다. 아무튼, 코드를 분석해 보면서 배운 점이 많았는데요. 그 중에, IntStream의 range와 rangeClose에 대해 알아봅시다.

 


 먼저, range는 startInclusive부터, endExclusive-1까지 1씩 증가하는 속성을 가지는 sequential한 Intstream을 생성합니다. 이와 비슷한 함수인, rangeClosed라는 함수가 있는데요.

 

 rangeClosed는, startInclusive부터 endInclusive까지 1씩 증가하는 속성을 가지는 IntStream을 생성합니다. 다른 것은 전자는 [s, e)인 반면, 후자는 [s, e]라는 것이 다릅니다. 전자는 s 이상 e 미만, 후자는 s 이상 e 이하입니다.

 

 다음에 mapToObj인데요. 이 글에서는 깊게 다루지는 않겠습니다. 간단하게 요약하면, 어떠한 값이 들어왔을 때, 어떤 값으로 apply를 시킬 건지를 정의합니다. 중간 연산이라고 합니다.

 

 toList 메서드로, list로 뽑아냅니다. 스트림에 있는 것을 잘 가공하고 필터링 해서 List에 넣습니다. 종단 연산이라 되어 있습니다. 실제로 이 함수가 호출될 때, mapToObj 내에 있는 것이 호출됩니다.

 

 예제 1번 프로그램을 보겠습니다. 이 프로그램은 0부터 4까지 1씩 증가하는 속성을 가지는 Intstream을 생성합니다. 그리고 메서드 mapToObj로 k라는 값이 들어왔을 때, 2를 곱한 값이 나오게끔 하는 또 다른 stream을 리턴합니다. 중간 연산이라 합니다.

 

 최종적으로, toList를 통해, [0, 2, 4, 6, 8]이 리턴됩니다. 종단 연산이라고 하고, 실제로, mapToObj 안에 있는 람다 식이 호출되는 시기는 toList 같은 종단 연산이 호출될 때입니다. 그러면, range(0, 5)는 대체 어디에 쓰일까요?

 

 


 foo 함수를 새로 정의하겠습니다. 그리고, mapToObj에 k -> foo(k)라 정의하겠습니다. 다른 건 없고, k라는 값을 받으면 2배를 취하는 함수입니다. 12번째 줄에 break point를 걸고 하나씩 보겠습니다.

 

 

 먼저, AbstractPipeline의 508번째 줄을 보면, wrappedSink가 보이는데요. 여기서 이 정보를 가지고 forEachRemaining 메서드를 수행함을 알 수 있어요.

 

 해당 object에는 from, upTo, last 등이 있습니다. 내부를 쭉쭉 타고 들어가 봅시다.

 

 

 Streams의 95번째 줄을 봅시다. 그러면, 이런 부분이 떡하니 보이는데요. 특정 횟수동안 consumer.accept를 호출함을 볼 수 있어요. 내부를 타고 들어가면, 이런 코드들이 보이는데요.

 

 

 여기서 mapper는 k -> foo(k)를 의미합니다. apply는 k라는 값을 받아서 foo(k)를 리턴하는 것입니다. 여기까지 대략적으로 파악한 내용을 종합해서 정리를 하면, toList와 같은 종단 연산을 수행할 때, Intstream.range(0, 5)로 생성한 시작 0, 끝 수 4, step 1이라는 속성이 stream에 반영되었고, mapToObj 등으로 어떻게 변환을 시킬 건지에 대해서 정의를 한 것이 추가로 반영이 된 셈입니다.

 

 결국, 위 코드는 foo(0), foo(1), foo(2), foo(3), foo(4)를 수행한 결과를 list로 반환하게 됩니다.

 

 


 함수를 n번 호출하려면 어떻게 하면 될까요? 예를 들어, ["rest1", "rest2", ... , "rest10"] 으로 채우고 싶습니다. 이럴 때에도 range와 IntStream은 굉장히 유용하게 쓰일 수 있습니다.

 

 

 1에서 시작하고 10에서 끝나고, step이 1씩 증가하는 속성을 가진 stream을 생성합니다. 그리고, k라는 값이 들어오면, k 앞에 "rest" 라는 문자열을 붙이는 속성을 추가한 stream을 생성합니다. 여기서 주목해야 할 것은 mapToObj인데요. k가 들어오면, "rest" + k라는 것을 리턴하는 것을 단순히 10번 수행했습니다. 

 

 

 결과는 위와 같습니다.