: 데이터 소스를 추상화하고 자주 사용되는 메서드들을 정의해 놓은 것
특징
Side effect: 기존 데이터 소스를 변경하지 않는다. 따라서 stream을 사용하면 여러 스레드가 동시접근해도 임계영역 문제를 줄일 수 있다.
일회용: 한번 사용하면 스트림이 닫혀서 재사용 불가능하다. 필요하면 스트림을 다시 생성해야한다.
데이터 소스 추상화 : 데이터 소스가 무엇이던 같은 방식으로 다룰 수 있다. 원래같으면 List 정렬에는
Collections.sort()
, Array 정렬에는Arrays.sort()
사용해야한다.지연 연산(lazy evaluation): 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다. 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모된다.
1 2 3 4 5 6
List<String> names = Arrays.asList("a", "b", "c"); lists.stream().map(name -> { System.out.println(name); // * return name.toUpperCase(); });
- 위 코드에서
map()
이라는 중간 연산만 존재하고 최종 연산이 없기 때문에*
출력문은 수행되지 않는다.
- 위 코드에서
- 오토박싱 & 언박싱의 데이터 소스의 요소를 기본형으로 다루는 스트림 (IntStream, LongStream, DoubleStream)이 제공된다.
- 오토박싱과 언박싱: int -> Integer, Integer -> int
- 병렬 스트림: 스레드 병렬 처리를 지원한다.
parralel()
로 병렬 처리,sequential()
로 병렬 처리하지 않도록.
스트림 생성
Collection.stream()
,Stream.of(T... values)
과 같이 생성기본형 스트림(박싱되어있는 스트림)이 일반 스트림보다 보편적으로 좋다. e.g)
IntStream
이Stream<Int>
보다 좋다. 해당 타입의 값으로 작업하는데 유용한 메서드들이 포함되어있다.- 중간 연산인
mapTo()
를 통해 기본형 스트림으로 변경해준다.
- 중간 연산인
iterate()
,generate()
: 람다식을 매개변수로 받아, 이 람다식에 의해 계산되는 값들을 요소로하는 무한 스트림을 생성1 2 3 4 5 6
Stream<Double> randomStream = Stream.generate(Math::random); Stream<Double> oneStream = Stream.generate(() -> 1)); IntStream evenStream = Stream.iterate(0, n -> n + 2); // A. 에러 IntStream evenStream = Stream.iterate(0, n -> n + 2).mapToInt(Integer::valueOf); Stream<Integer> stream = evenStream.boxed(); // IntStream -> Stream<Integer>
- A :
iterate()
,generate()
에 의해 생성된 스트림을 기본형 스트림 타입의 참조변수로 다룰 수 없다.
- A :
Optional
- 객체 생성:
Optional.of()
orOptional.ofNullable()
- 객체 값 가져오기:
optionalObj.get()
. Null이면 NPE 발생하기 때문에orElse()
로 값 대체 ororThrow()
isPresent()
를 통해 조건식 가능.ifPresent()
를 사용하면 더 간결하다.ifPresent()
는findAny()
,findFirst()
같은 최종 연산과 잘 어울린다.
중간 연산
- 연산 결과가 스트림이다.
- 스트림 자르기 (앞)
skip()
, (뒤)limit()
, 요소 걸러내기filter()
,distinct()
, 조회peek()
: 스트림 중간에 로그 찍기 용도 등. flatMap()
:Stream<T[]>
->Stream<T>
최종 연산
- 연산 결과가 스트림이 아니다.
- loop
forEach()
, 조건 검사~~~Match()
,find~~~()
, 통계나 연산count()
,sum()
,average()
,max()
,min()
등 - 리듀싱:
reduce()
. 스트림의 요소를 줄여나가면서 연산을 수행하고 최종 결과 반환한다.- 매개변수 타입이
BinaryOperator<T>
이다. 처음 두 요소를 가져와 연산을 수행하고 그 결과를 가지고 다음 요소와 연산한다. count()
,sum()
,max()
,min()
은 내부적으로 리듀싱을 사용한다.
- 매개변수 타입이
collect
: 스트림의 요소를 수집하는 최종 연산. reducing과 유사하다.
Collectors 메서드
스트림을 컬렉션, 배열로 변환:
toList()
,toSet()
등통계:
counting()
,summingInt()
,averaginInt()
,maxBy()
,minBy()
리듀싱:
reducing()
문자열 결합:
joining()
구분자, 접두사, 접미사 지정도 가능하다.그룹화와 분할:
groupingBy()
,partitioningBy()
. 이 두 가지가collect()
가 필요한 가장 큰 이유이다. 둘은 분류를 Function으로 하느냐, Predicate로 하느냐의 차이만 있을 뿐 동일하다.groupingBy()
: 특정 요소를 기준으로 그룹화.partitioningBy()
: 지정된 조건에 일치하는 그룹과 아닌 그룹 두가지로 분할. Predicate를 매개변수로 받는다. 두 개의 그룹으로 나눠야 한다면 당연히PartitioningBy()
를 사용하는 것이 빠르다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
stream.collect(summingInt(Foo::getNumber)); // -- 아래 두 방식은 동일한 결과 stream.map(Foo::getNumber).reduce(0, Integer::sum); stream.collect(reducing(0, Foo::getNumber, Integer::sum)); // --- 그룹화와 분할 예시 Map<Boolean, Long> stuNumBySex = stuStream .collect(partitioningBy(Student::isMale)); Map<Integer, List<Student>> stuByClass = stuStream .collect(groupingBy(Student::getClass)); Map<Integer, Map<Integer, List<Student>>> stuByGradeAndClass = stuStream .collect(groupingBy(Student::getGrade, groupingBy(Student:getClass) ));
Collector 구현
: “컬렉터를 작성한다” == “Collector 인터페이스를 구현한다.”
supplier()
,accumulator()
,combiner()
,finisher()
,characteristics()
중 앞 네 개는 함수형 인터페이스이다. 따라서 해당 부분에 람다식을 작성하면 된다.- 앞의 세 개 인터페이스는 reduce 인터페이스에서도 등장하는 개념이다. 따라서, 내부적으로 처리하는 과정이 리듀싱과 같다. 그룹화와 분할, 집계 등에 collector가 유용하게 쓰이고, 병렬화에 있어서도
collect()
가 유리하다.
Reference)
자바의 정석 3판