brunch

You can make anything
by writing

C.S.Lewis

by 이승현 Feb 18. 2019

Effective Java3/E - 람다와 스트림

#45 스트림은 주의해서 사용하라

Effective Java 3/E - 람다와 스트림


#45 스트림은 주의해서 사용하라


Java 8에서 다량의 데이터 처리 작업을 돕고자 Stream API가 추가되었습니다.


스트림 API는 메서드 연쇄를 지원하는 Fluent API입니다.

Fluent는 한글로 "유창한"이라는 의미를 가지고 있습니다.
코드를 작성하면, 마치 영어 문장처럼 잘 읽히는 API를 뜻합니다.


따라서 스트림을 제대로 사용하면 코드가 간결하고 깔끔해지지만, 잘못 사용하면 오히려 가독성이 떨어지고 유지보수도 힘들어집니다.


이 스트림을 이용 시 주의할 점에 대해 알아보겠습니다.




#01 스트림


먼저 스트림 API가 제공하는 추상 개념 중 스트림에 대해 알아보겠습니다.


스트림(Stream) : 데이터 원소의 유한 혹은 무한 시퀀스(sequence)


스트림을 생성하기 위한 원소들은 어디서든 올 수 있습니다.

대표적으로 컬렉션, 배열, 파일, 정규표현식 패턴 matcher, 난수 생성기, 혹은 다른 스트림이 있습니다.


그리고 스트림 안의 데이터 원소들은 객체 참조나 int, long, double 기본 타입입니다.




#02 스트림 파이프라인


스트림 API가 제공하는 추상 개념으로 스트림 파이프라인도 있습니다.


스트림 파이프라인(Stream pipeline) :
데이터 원소들로 수행하는 연산 단계를 표현


#01 스트림 파이프라인 예시


스트림 파이프라인에는 중간 연산과 종단 연산이 있습니다.

그리고 하나의 종단 연산과 하나 이상의 중간 연산이 있을 수 있습니다.

종단 연산(terminal operation) : 마지막 중간 연산이 내놓은 스트림에 가하는 마지막 연산

중간 연산(intermediate operation) : 스트림을 어떠한 방식으로 변환하는 연산


스트림 파이프라인은 지연 평가(lazy evaluation)가 됩니다.

지연 평가 : 계산의 결괏값이 필요할 때까지 계산을 늦추는 기법


종단 연산이 호출될 때 평가가 이뤄지기 때문에 종단 연산을 빼먹으면 아무 일도 하지 않습니다.




#03 과도한 Stream 사용 예시


#02 Anagram class

사전 File에서 단어들을 읽어와 사용자가 지정한 값보다 원소수가 많은 Anagram 그룹을 출력하는 코드입니다.


이를 아래와 같이 Stream API를 이용해 구현할 수 있습니다.

이처럼 스트림을 과도하게 이용하면 오히려 가독성도 떨어지고 유지보수도 힘들어집니다.


#03 Anagram stream class


스트림을 적절하게 이용하면, 아래와 같이 코드도 짧아지고 의미도 명확해집니다.

람다를 이용하기 때문에 타입이 생략되므로 매개변수 이름을 잘 지어야 가독성이 좋아집니다.

그리고 alphabetize와 같은 도우미 메서드들도 적절히 이용하는 것도 중요합니다.


#04 Anagram stream class


스트림을 처음 접하면 모든 코드를 스트림으로 바꾸고 싶어 질 때가 있는데, 자칫하면 가독성과 유지보수 측면에서 더 안 좋을 수 있기 때문에 주의해야 합니다.




#04 When Stream?


책에는 아래와 같이 Stream이 알맞은 경우들을 정리했습니다.


원소들의 시퀀스를 일관되게 변환한다.

원소들의 시퀀스를 필터링한다.

원소들의 시퀀스를 하나의 연산을 사용해 결합한다. (더하기, 연결하기, 최솟값 구하기 등)

원소들의 시퀀스를 Collection에 모은다.  (공통된 속성을 기준으로 묶어가며)

원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다. 


반대로 Stream이 알맞지 않은 경우도 있습니다.

아래 코드처럼 한 스트림 파이프라인(peek)에서 각 단계의 값들에 동시에 접근할 수 없습니다.

스트림 파이프라인은 일단 한 값을 다른 값에 매핑하고 나면 원래의 값을 잃는 구조이기 때문입니다.


#05 compile error




#05 Stream vs 반복문


#06 Stream vs 반복문


되풀이되는 계산을 스트림을 이용해 람다나 메서드 참조로 구현하거나, 기존 반복문을 통해 구현할 수 도 있습니다.


하지만 람다나 메서드 참조로는 지역 변수를 읽거나 수정할 수 없습니다. final 변수만 읽을 수 있습니다.

게다가 return, break, continue문을 통해 반복문을 종료하거나 건너뛸 수 도 없습니다.

이러한 제한 사항까지 고려해서 Stream과 반복문을 선택해야 합니다.


하지만 경우에 따라 스트림과 반복문 중 어떤 방식으로 구현할지 애매할 수 도 있습니다.

아래 코드는 두 집합의 원소들로 만들 수 있는 모든 조합을 계산(데카르트 곱)하는 코드입니다.


취향에 따라 반복문이 더 편한 개발자도 있고, 스트림이 더 편한 개발자도 있습니다.

이럴 때는 사실 정답이 없습니다. 각자의 취향에 따라 어떤 방식으로든 구현할 수 있습니다.


#07 반복문 & 스트림



결론은 스트림과 기존 코드 중 어느 쪽이 더 나은지 판단하기 어렵다면, 직접 구현해보고 판단하라입니다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari