왜 사용할까
자바 8부터 람다를 활용하여 함수형 프로그래밍이 가능해졌다.
컬렉션에 존재하던 엘리먼트들을 순회하면서 처리할 수 있는 패턴이다.
장점은 람다식과 함께 사용되어 데이터에 대한 처리를 간결하게 표현하여 코드양을 줄고 가독성이 좋아진다.
람다(lambda)란?
람다의 핵심은 (파라미터) -> {수행할 코드} 를 통해 메소드를 정의하지 않고도 메소드처럼 사용할 수 있다는 것인데
메소드와 같은 기능이지만 정의하지 않고도 사용하는 것이 람다의 핵심이 되겠다
Collection 과 Stream의 차이점은 데이터 계산 시점 !
Collection
- 모든 값을 메모리에 저장하는 자료구조다. 따라서 Collection에 추가하기 전에 미리 계산이 완료되어있어야 한다.
- 외부 반복을 통해 사용자가 직접 반복 작업을 거쳐 요소를 가져올 수 있다(for-each)
Stream
- 요청할 때만 요소를 계산한다. 내부 반복을 사용하므로, 추출 요소만 선언해주면 알아서 반복 처리를 진행한다.
- 스트림에 요소를 따로 추가 혹은 제거하는 작업은 불가능하다.
외부 반복 & 내부 반복
Collection은 외부 반복, Stream은 내부 반복이라고 했다. 두 차이를 알아보자.
성능 면에서는 '내부 반복'이 비교적 좋다. 내부 반복은 작업을 병렬 처리하면서 최적화된 순서로 처리해준다.
하지만 외부 반복은 명시적으로 컬렉션 항목을 하나씩 가져와서 처리해야하기 때문에 최적화에 불리하다.
즉, Collection에서 병렬성을 이용하려면 직접 synchronized를 통해 관리해야만 한다.
Stream 연산
스트림은 선언, 중간연산, 최종연산 세 부분으로 이루어진다.
차례차례 알아보자.
1. 선언
배열, 컬렉션(list, map, set)을 스트림으로 만들기
ex)
Stream<데이터타입> stream명 = Arrays.stream(배열명);
Stream<데이터타입> stream명 = 리스트명.stream();
Stream<데이터타입> stream명 = Stream.of('값', '값'....);
각각 배열과 컬렉션을 사용하는 경우
아니면 직접 값을 넣어 사용하는 경우인데
꼭 stream을 선언한 후 값을 넣고 사용하는 것이 아니라
Arrays.stream(배열명).가공메소드...
리스트명.stream.가공메소드...
이런 식으로 바로 사용해도 된다
2. 가공
생성한 Stream 객체에서 요소들을 가공하기 위해서는 중간연산이 필요하다. 가공하기 단계의 파라미터로는 앞서 설명하였던 함수형 인터페이스들이 사용되며, 여러 개의 중간연산이 연결되도록 반환값으로 Stream을 반환한다.
ex)
- filter(Predicate) : Predicate를 인자로 받아 true인 요소를 포함한 스트림 반환
- distinct() : 중복 필터링
- limit(n) : 주어진 사이즈 이하 크기를 갖는 스트림 반환
- skip(n) : 처음 요소 n개 제외한 스트림 반환
- map(Function) : 매핑 함수의 result로 구성된 스트림 반환
- flatMap() : 스트림의 콘텐츠로 매핑함. map과 달리 평면화된 스트림 반환
3. 최종연산
중간 연산을 통해 생성된 Stream을 바탕으로 이제 결과를 만들 차례이다. 결과를 만들기 위한 최종 연산에는 다음과 같은 것들이 존재한다.
- (boolean) allMatch(Predicate) : 모든 스트림 요소가 Predicate와 일치하는지 검사
- (boolean) anyMatch(Predicate) : 하나라도 일치하는 요소가 있는지 검사
- (boolean) noneMatch(Predicate) : 매치되는 요소가 없는지 검사
- (Optional) findAny() : 현재 스트림에서 임의의 요소 반환
- (Optional) findFirst() : 스트림의 첫번째 요소
- reduce() : 모든 스트림 요소를 처리해 값을 도출. 두 개의 인자를 가짐
- collect() : 스트림을 reduce하여 list, map, 정수 형식 컬렉션을 만듬
- (void) forEach() : 스트림 각 요소를 소비하며 람다 적용
- (Long) count : 스트림 요소 개수 반환
스트림 예시
스트림의 단점
1. 디버그가 힘들다
뭘 잘못 짰거나 에러가 발생한다면
일반 코드의 경우에는 값이 틀어지기 직전,
에러가 나기 직전에 디버그를 걸어놓으면
손쉽게 디버깅이 가능한데
스트림은 한번에 모든 것이 수행되기 떄문에
에러라도 난다고 치면
스트림을 다 뜯어놓고 다시 조립을 해야 한다
가장 문제는 만든 사람이야 그나마 이해가 빠르지만
아예 모르는 사람이 스트림 코드를 디버깅할라면
굉장히 힘들다는 점이다
2. 재활용 불가능
스트림은 한번 쓰면 close 되기 때문에
Stream<> 스트림명 = 값;
이런 식으로 한번 정의해놓고 계속 사용이 불가능하다
끝으로 스트림 메소드가 엄청 많으므로
하나씩 예시를 보면서 연습해보는 시간을 가져야겠다.
ref)
https://mangkyu.tistory.com/114
https://wakestand.tistory.com/419
https://gyoogle.dev/blog/computer-language/Java/Stream.html
'Java 공부' 카테고리의 다른 글
자바의 데이터 타입(Primitive type, Reference type) (0) | 2022.02.28 |
---|---|
Java 컴파일 과정 (0) | 2022.02.28 |
동기화(Synchronized ) vs 비동기화(Asynchronized) / 블로킹(blocking)과 논블로킹(non-blocking) (0) | 2021.11.14 |
Java 객체 직렬화(Serialization) 와 역직렬화(Deserialization) (0) | 2021.11.14 |
자바 스레드(Thread) 정리글 (0) | 2021.11.14 |