-
item 45 스트림은 주의해서 사용하라책/이펙티브 자바 2022. 4. 11. 17:06
ITEM 45 스트림은 주의해서 사용하라
스트림 API는 다량의 데이터 처리 작업(순차적이든 병렬적이든)을 위해 java8에 추가됐다
스트림 API가 제공하는 추상 개념 중 핵심은 두가지다
- 스트림(stream)은 데이터 원소의 유한 혹은 무한 시퀀스(sequence)를 뜻한다
- 스트림 파이프라인(stream pipeline)은 이 원소들로 수행하는 연산 단계를 표현하는 개념이다
스트림의 원소들은 어디로부터든 올 수 있으며 대표적으로 컬렉션, 배열, 파일, 정규표현식 패턴 매처(matcher),난수 생성기, 혹은 다른 스트림이 있다
스트림 안의 데이터 원소들은 객체 참조나 기본 타입 값이다
- 기본 타입 값으로는 int, long, double 세가지를 지원한다
스트림 파이프라인은 소스 스트림에서 시작해 종단 연산으로 끝나며 그 사이에 하나 이상의 중간 연산이 있을 수 있다
- 각 중간 연산은 스트림을 어떠한 방식으로 변환한다
- 예로 각 원소에 함수를 적용하거나 특정 조건을 만족 못하는 원소를 걸러낼 수 있다
- 중간 연산들은 모두 한 스트림을 다른 스트림으로 변환하는데 변환된 스트림의 원소 타입은 변환 전 스트림의 원소 타입과 같을 수도 있고 다를 수도 있다
- 종단 연산은 마지막 중간 연산이 내놓은 스트림에 최후의 연산을 가한다
- 원소를 정렬해 컬렉션에 담거나 특정 원소 하나를 선택하거나 모든 원소를 출력하는 식이다
스트림 파이프라인은 지연 평가 된다
- 평가는 종단 연산 호출시 이뤄진다
- 종단 연산에 쓰이지 않는 데이터 원소는 계산에 쓰이지 않는다
- 이런 지연 평가가 무한 스트림을 다룰 수 있게 해주는 열쇠다
- 종단 연산이 없는 스트림 파이프라인은 아무 일도 하지 않는 명령어인 no-op와 같으니 종단 연산을 빼먹지 말자
스트림 API는 메서드 연쇄를 지원하는 플루언트 API다
- 파이프라인 하나를 구성하는 모든 호출을 연결하여 단 하나의 표현식으로 완성할 수 있다
- 파이프라인 여러 개를 연결해 표현식 하나로 만들 수도 있다
스트림 파이프라인은 순차적으로 수행된다
- 파이프라인을 병렬로 실행하려면 파이프라인을 구성하는 스트림 중 하나에서 parallel 메서드를 호출하면 된다
- 막상 효과를 볼 수 있는 상황이 그리 많지가 않다
스트림 API는 다재다능하여 사실상 어떠한 계산이라도 해낼 수 있다
- 계산을 할 수 있는거지 꼭 해야 하는건 아니다
- 스트림을 제대로 사용하면 프로그램이 짧고 깔끔해진다
- 반대로 잘못 사용하면 읽기 어렵고 유지보수도 힘들어 진다
스트림을 처음 사용하면 모든 반복문을 스트림으로 바꾸고 싶은데 서두르지 말자
- 스트림으로 바꾸는게 가능해도 오히려 가독성과 유지보수성이 떨어지는 경우가 있을 수 있다
- 중간 정도 복잡한 작업에도 스트림과 반복문을 적절하게 조합하는게 좋다
- 기존 코드는 스트림을 사용하도록 리팩터링을 해보고 새 코드가 더 나아 보이면 그걸로 사용하자
스트림 파이프라인은 되풀이되는 계산을 함수 객체(람다나 메서드 참조)로 표현하고 반복 코드에서는 코드 블록을 사용해 표현한다
- 함수 객체로 할수 없지만 코드 블록에선 가능한 작업 (람다로는 모두 불가능하다, 계산 로직에서 아래의 일을 수행해야 되면 스트림과 맞지 않다)
- 코드 블록에서는 범위 안의 지역변수를 읽고 수정 가능
- 람다에서는 final이거나 사실상 final인 변수만 읽을 수 있으며 지역변수를 수정할 수 없다
- 코드 블록에서는 return 문을 사용해 메서드에서 빠져 나갈 수 있다
- break가 continue 문으로 블록 바깥의 반복문을 종료하거나 반복을 한 번 건너뛸 수도 있다
- 메서드 선언에 명시된 검사 예외를 던질 수도 있다
- 스트림을 사용하면 좋은 작업(아래와 같은 일을 수행하는 작업은 스트림을 사용하기에 좋다)
- 원소들의 시퀀스를 일관되게 변환한다
- 원소들의 시퀀스를 필터링한다
- 원소들의 시퀀스를 하나의 연산을 사용해 결합한다(더하기, 연결하기, 최솟값 구하기 등...)
- 원소들의 시퀀스를 컬렉션에 모은다(공통된 속을 기준으로 묶는다)
- 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다
- 스트림으로 처리하기 어려운 작업
- 한 데이터가 파이프라인의 여러 단계(stage)를 통과할 때 이 데이터의 각 단계에서의 값들에 동시에 접근하는 경우
- 스트림 파이프라인은 일단 한 값을 다른 값에 매핑하고 나면 원래의 값은 잃는 구조다
- 원래 값과 새로운 값의 쌍을 저장하는 객체를 사용해 매핑하는 우회적인 방법도 있으나 별로 만족스럽지는 않다
- 매핑 객체가 필요한 단계가 여러곳이면 더욱 별로다 (코드의 양도 많을 뿐더러 지저분하다)
- 가능하다면 앞 단계의 값이 필요할 때 매핑을 거꾸로 수행하는게 낫다
- 스트림 파이프라인은 일단 한 값을 다른 값에 매핑하고 나면 원래의 값은 잃는 구조다
- 한 데이터가 파이프라인의 여러 단계(stage)를 통과할 때 이 데이터의 각 단계에서의 값들에 동시에 접근하는 경우
스트림을 사용해야 멋지게 처리할 수 있는 일이 있고 반복 방식이 더 알맞은 일도 있으며 많은 작업들은 이 두가지를 조합하면 멋있게 해결된다
어느 쪽을 선택해야 하는가에 확실한 규칙은 없지만 참고할 지침은 있다
어느 쪽이 나은지가 확연하게 드러나는 경우가 많지만 아니라도 방법은 있다
스트림과 반복 중 어느게 나은지 모르겠으면 둘다 해보고 더 나은걸 선택하자
'책 > 이펙티브 자바' 카테고리의 다른 글
item 47 반환 타입으로는 스트림보다 컬렉션이 낫다 (0) 2022.04.12 item 46 스트림에서는 부작용 없는 함수를 사용하라 (0) 2022.04.12 item 44 표준 함수형 인터페이스를 사용하라 (0) 2022.04.11 item 43 람다보다는 메서드 참조를 사용하라 (0) 2022.04.11 item 42 익명 클래스보다는 람다를 사용하라 (0) 2022.04.06