📌Java Stream
STREAM은 람다식과 함께 자바 8에서 추가된 “함수형 프로그래밍을 지원하기 위한 클래스로, Java의 컬렉션 데이터에 대해 SQL 질의문 처럼 데이터를 처리할수 있는 기능이다. 즉, 선언형으로 컬렉션 데이터를 처리할 수 있는 것이다. '데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소'로 정의하기도 한다. 기존 자바 I/O에서 나오는 InputStream, OutputStream과는 다른것으로 함수형 프로그램에서 단계적으로 정의된 계산을 처리하기 위한 인터페이스이다.
기존 Java에서 컬렉션 데이터를 처리할때는 for, foreach 루프문을 사용하면서 컬렉션 내의 요소들을 하나씩 다루었다. 간단한 처리나 컬렉션의 크기가 작으면 큰 문제가 아니지만 복잡한 처리가 필요하거나 컬렉션의 크기가 커지면 루프문의 사용은 성능저하를 일으킨다. 컬렉션 데이터를 처리할때 특정 조건에 따라 필터링을 하기 위해 복잡한 과정을 거치는 대신, 스트림을 이용하면 컬렉션 데이터를 선언형으로 쉽게 처리 할 수 있는 것이다.
즉, 기본적으로 스트림이 가진 특징인 코드의 간결성과 자유로운 블럭 형태의 메소드로 인해 조합이 편리하다는 점이 가장 큰 장점이고, 그로 인해 코드의 작성 속도가 빨라지고 가독성이 높아진다는 것이 스트림 사용 이유의 가장 큰 목적이다.
📖스트림의 특징
- 데이터를 담고 있는 저장소(컬렉션)이 아니다.
- 스트림은 원본 데이터 소스를 변경하지 않는다.(Read Only)
- 스트림은 Iterator처럼 일회용이다. (필요하면 다시 스트림을 생성해야 함)
- 최종 연산 전까지 중간연산을 수행되지 않는다.(lazy)
- 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.)
- 손쉽게 병렬 처리할 수 있다. (멀티 쓰레드 사용) (
.parallel
)
- 기본형 스트림으로
IntStream
, LongStream
, DoubleStream
등 제공
- 오토박싱 등의 불필요한 과정이 생략됨.
Stream<Integer>
대신에 IntStream
을 사용하는게 더 효율적이다.
- 뿐만 아니라 숫자의 경우 더 유용한 메서드를
Stream<T>
보다 더 많이 제공한다.(.sum()
, .averge()
등)
📖스트림 vs 컬렉션
자바에서 제공하는 모든 컬렉션의 최고 상위 조상인 Collection 인터페이스에는 stream() 메소드가 정의되어 있다. 따라서 , Collection 인터페이스를 구현한 모든 List와 Set 컬렉션 클래스에서도 stream() 메소드로 스트림을 생성할 수 있다.
📖스트림 구조
스트림에 대한 내용은 크게 세 가지로 나눌 수 있다.
- 생성하기 : 스트림 인스턴스 생성.
- 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
- 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).
📚1. 생성하기
Stream 객체를 생성하는 단계
- 배열 : Array.stream() / 컬렉션 : stream() / 빈 스트림
- Stream.builder() : 스트림에 직접적으로 원하는 값을 넣기 Stream.generate() : Supplier를 인수로 받아서 새로운 값을 생산 Stream.iterate() : 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어갈 요소를 만들기. 요청할때마다 값을 생산
- 기본 타입형 / String / 파일 스트림
- 병렬 스트림 : parallelStream() / 스트림 연결하기 : Stream.concat()
📚2. 가공하기
원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산. 연산 결과를 Stream으로 다시 반환하기 때문에 연속해서 중간 연산을 이어갈 수 있다.
- Filtering (스트림 내 요소들을 하나씩 평가해서 걸러내는 작업)
ex) .filter(a → name.contains(”a”)) : ‘a’ 가 들어간 이름만 들어간 스트림이 리턴
- Distinct (중복을 제거하고 스트림을 반환)
ex) .distinct()
- Mapping (스트림 내 요소들을 하나씩 특정 값으로 변환)
ex)
.map(String::toUpperCase) : 대문자로 변환한 값들이 담긴 스트림을 리턴
.map(Product::getAmount) : 요소 내 들어있는 Product 개체의 수량을 꺼내오기. 각 ‘상품’을 ‘상품의 수량’으로 맵핑
.flatMap() : 기존의 요소를 새로운 요소로 대체한 스트림을 생성
- Sorting
ex) .sorted() : 오름차순 정렬
- Iterating (스트림 내 요소들 각각을 대상으로 특정 연산.스트림 내 요소들 각각에 특정 작업을 수행할 뿐 결과에 영향을 미치지 않음)
ex) peek() : 중간에 결과를 확인해볼 때 사용
📚3. 결과 만들기
가공된 데이터로부터 원하는 결과를 만들기 위한 최종 연산. Stream의 요소들을 소모하면서 연산이 수행되기 때문에 1번만 처리가능하다.
- Calculating
- 최소 : .min()
- 최대 : .max()
- 합 : .sum()
- 평균 : average()
- Reduction : 모든 스트림 요소를 처리해서 값으로 도출
.reduce(초기값, 두 요소를 조합해서 새로운 값으로 만들어낼 형태)
→ 초기값없으면 Optional 객체 반환
- Collecting .collect(Collectors.toList()) : 작업한 결과를 담은 리스트로 반환 .collect(Collectors.joining()) : 작업한 결과를 하나의 스트링으로 이어 붙이기 .collect(Collectors.averagingInt()) : 숫자 값의 평균을 내기 .collect(Collectors.summingInt()) :숫자값의 합 .collect(Collectors.summarizingInt()) : 숫자값의 합, 평균, 최소, 최대 .collect(Collectors.groupingBy())
- Matching : 조건식 람다 Predicate 를 받아서 해당 조건을 만족하는 요소가 있는지 체크한 결과를 리턴
.anyMatch() : 하나라도 조건을 만족하는 요소가 있는지
.allMatch() : 모두 조건을 만족하는지
.noneMatch() : 모두 조건을 만족하지 않는지
- Iterating : 요소를 돌면서 실행되는 최종 작업. 보통 System.out.println 메소드를 넘겨서 결과를 출력할 때 사용.
.forEach(System.out::println);