요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다. 컬렉션 및 배열의 요소를 반복처리하기 위해 사용한다.
Stream은 Iterator와 비슷한 반복자이지만, 차이점을 가지고 있다.
for 문과 Iterator : 컬렉션의 요소를 컬렉션 바깥쪽으로 가져와 처리하기때문에 외부 반복자라고 부른다.
스트림 : 요소 처리 방법을 컬렉션 내부로 주입시켜 요소를 반복 처리하기에 내부 반복자라고 한다.
외부 반복자일 경우
두 가지를 모두 개발자 코드가 가지고 있어야 한다.
내부 반복자일 경우
를 가지고 컬렉션 내부에서 요소를 반복 처리한다.
스트림은 하나 이상 연결될 수 있다. 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결될 수 있다. 이와같이 스트림이 연결되어 있는 것을 파이프라인이라고 한다.
Stream<Student> studentStream = list.stream();
IntStream scroeStream = studentStream.mapInto(student -> student.getScore());
double avg = scoreStream.average().getAsDouble();
중간 스트림 : 필터링, 매핑, 정렬하는 작업 수행
최종 처리 : 중간 처리에서 정제된 요소들을 반복하거나, 집계 (카운팅, 총합, 평균) 작업 수행
mapToInt() : 객체를 int 값으로 매핑해서 IntStream으로 변환
메소드 체이닝 패턴을 사용해 위 코드를 간결하게 작성해보자.
double avg = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();
스트림 파이프라인으로 구성할 때 주의할 점은 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다는 것이다. 최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않는다.
즉, 위 코드에서
averge()이하를 생략하면stream(),mapToInt()는 동작하지 않는다.
java.util.Collection 인터페이스는 스트림과 parallelStream() 메소드를 가지고 있다. 따라서, 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.
java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있다.
String[] strArray = { "홍길동", "김미나" };
Stream<String> strStream = Arrays.stream(strArray);
strStream.forEach(item -> System.out.print(item + ","));
이 외에도 IntStream, LongStream의 정적 메소드인 range(), rangeClosed() 메소드를 이용해 특정 범위의 정수 스트림을 얻거나, java.nio.file.Files의 lines() 메소드를 통해 텍스트 파일의 행 단위 스트림을 얻을 수 있다.
distinct() -> 중복 제거filter() -> 조건 필터링mapXxx() 메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.
등의 종류가 있다. 매개 타입인 Function은 함수형 인터페이스이다.
모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx() 메소드를 가지고 있다.
flatMapXxx() 메소드는 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.
정렬은 요소를 오름차순 또는 내림차순으로 정렬하는 중간 처리 기능이다.
스트림의 요소가 객체일 경우 객체가 Comparable을 구현하고 있어야만 sorted() 메소드를 사용하여 정렬할 수 있다.
public class Xxx implements Comparable {
...
}
List<Xxx> list = new ArrayList<>();
list.stream().sorted();
만약 내림차순으로 정렬하고 싶다면
Stream<Xxx> reverseOrderedStream = stream.sorted(Comparator.reverseOrder());
Comparator.reverseOrder() 메소드가 리턴하는 Comparator를 매개값으로 제공하면 된다.
요소 객체가 Comparable을 구현하고 있지 않다면, Comparator 인터페이스를 구현한 객체인 비교자를 제공하면 요소를 정렬시킬 수 있다.
list.stream()
.sorted(s1, s2) -> Integer.compare(s2.getScore(), s1.getScore());
루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.
peek()forEach()peek()과 forEach()는 동일하게 요소를 루핑하지만 peek은 중간 처리 메소드이고, forEach는 최종 처리 메소드이다. 따라서 peek은 최종 처리가 뒤에 붙지 않으면 동작하지 않는다.
매개 타입인 Consumer는 함수형 인터페이스이다. 모든 Consumer는 매개값을 처리(소비)하는 accept() 메소드를 가지고 있다.
Arrays.stream(intArr)
.filter(a -> a % 2 == 0)
.peek(n -> System.out.println(n))
.sum();
Arrays.stream(intArr)
.filter(a -> a % 2 == 0)
.forEach(n -> System.out.rpintln(n));
매칭은 요소들이 특정조건에 만족하는지 여부를 조사하는 최종 처리 기능이다.
allMatch(Predicate) : 모든 요소가 만족하는지 여부anyMatch(Predicate) : 최소한 하나의 요소가 만족하는지 여부noneMatch(Predicate) : 모든 요소가 만족하지 않는지 여부매개값으로 주어진 Predicate가 리턴하는 값에 따라 true 또는 false를 리턴한다.
집계 (Aggregate) 는 최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값등과 같이 하나의 값으로 산출하는 것을 말한다. 즉, 대량의 데이터를 가공해서 하나의 값으로 축소하는 리덕션 (Reduction) 이라고 볼 수 있다.
long count() : 요소 개수findFirst() : 첫 번째 요소max() : 최대 요소min() : 최소 요소average() : 요소 평균sum() : 요소 총합count와 sum을 제외한 나머지 집계 메소드의 리턴값은 Optional이다. 이는 Optional, OptionalDouble, OptionalInt, OptionalLong 클래스를 말한다. 이들은 최종값을 저장하는 객체로 get(), getAsDouble(), getAsInt(), getAsLong()을 호출하면 최종값을 얻을 수 있다.
Optional, OptionalDouble, OptionalINt, OptionalLong 클래스는 단순히 집계값만 저장하는 것이 아니라, 집계값이 존재하지 않을 경우 디폴트 값을 설정하거나 집계값을 처리하는 Consumer를 등록할 수 있다.
isPresent() : 집계값이 있는지 여부orElse() : 집계값이 없을 경우 디폴트 값 설정ifPresent(Consumer) : 집계값이 있을 경우 Consumer에서 처리스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다.
List<Studnet> maleList = totalList.stream()
.filter(s->s.getSex().equals("남"))
.collect(Collectors.toList());
collect() 메소드는 단순히 요소를 수집하는 기능 이외에 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공한다. Collectors.groupingBy() 메소드에서 얻은 Collector를 collect() 메소드를 호출할 때 제공하면 된다.
Map<String, List>Studnet>> map = totalList.stream()
.collect(
Collectors.groupingBy(s -> s.getSex()) // 그룹핑 키 리턴
);
Collectors.groupingBy() 메소드는 그룹핑 후 매핑 및 집계를 수행할 수 있도록 두 번째 매개값인 Collector를 가질 수 있다.
mapping(Function, Collector) : 매핑averagingDouble(ToDoubleFunction) : 평균값counting() : 요소 수maxBy(Comparator) : 최대값minBy(Comparator) : 최소값reducing() : 커스텀 집계 값요소 병렬 처리, Parallel Operation이란 멀티 코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것을 말한다. 요소 병렬 처리의 목적은 작업 처리 시간을 줄이는 거에 있다.
멀티 스레드는 Concurrency 또는 Parallelism으로 실행된다.
동시성 : 멀티 작업을 위해 멀티 스레드가 하나의 코어에서 번갈아가며 실행하는 것
병렬성 : 멀티 작업을 위해 멀티 코어를 각각 이용해서 병렬로 실행하는 것
동시성은 한 시점에 하나의 작업만을 실행한다. 병렬성은 한 시점에 여러 개의 작업을 병렬로 실행하기 때문에 동시성보다는 좋은 성능을 낸다.
병렬성 = 데이터 병렬성 + 작업 병렬성
데이터 병렬성
전체 데이터를 분할해서 서브 데이터셋으로 만들고 이 서브 데이터셋들을 병렬 처리해서 작업을 빨리 끝내는 것을 말한다.
작업 병렬성
서로 다른 작업을 병렬 처리 하는 것을 말한다. 예시로, 서버는 각각의 클라이언트에서 요청한 내용을 개별 스프레드에서 병렬로 처리한다.
자바 병렬 스트림은 요소들을 병렬 처리하기 위해 포크조인 프레임워크를 사용한다.
포크조인 프레임워크는 포크 단계에서 전체 요소들을 서브 요소셋으로 분할하고, 각각의 서브 요솟셋을 멀티 코어에서 병렬로 처리한다. 조인 단계에서는 서브 결과를 결합해서 최종 결과를 만들어낸다.
포크조인 프레임워크는 병렬 처리를 위해 스레드풀을 사용한다. 각각의 코어에서 서브 요소셋을 처리하는 것은 작업 스레드가 해야 하므로 스레드 관리가 필요하다. 포크조인 프레임워크는 ExecutorService의 구현 객체인 ForkJoinPool을 사용해서 작업 스레드를 관리한다.
parallelStream()parallel()parallelStream() 메소드는 컬렉션으로부터 병렬 스트림을 바로 리턴한다. parallel() 메소드는 기존 스트림을 병렬 처리 스트림으로 변환한다.
스트림 병렬 처리가 스트림 순차 처리보다 항상 실행 성능이 좋다고 판단해서는 안 된다. 그 전에 먼저 병렬 처리에 영향을 미치는 위 세 가지 요인을 잘 살펴보아야 한다.