스트림 패러다임의 핵심은 계산을 일련의 변환(transformation)으로 재구성하는 부분이다.
이때 각 변환 단계는 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다.
→ 스트림 연산에 건네는 함수 객체는 모두 부작용이 없어야 가능하다. (중간, 종단 단계 모두)
//텍스트 파일에서 단어별 수를 세어 빈도표로 만드는 일
//API만 사용한 반복적 코드다. -> 스트림 이점 살리지 못함
//같은 기능의 반복적 코드보다 길고, 읽기 어렵고, 유지보수에 좋지 않음
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
//모든 작업이 종단 연산인 foEach에서 일어나는데 외부 상태를 수정하는 람다를 실행
// -> foreach가 스트림이 수행한 연산 결과를 보여주는 일 이상을 한다.
words.forEach(word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
});
}
//forEach 연산은 스트림 계산 결과를 보고할 때만 사용하고, 계산하는 데는 사용하지 말것!
//(가끔은 스트림 게산 결과를 기존 컬렉션에 추가하는 등의 다른 용도로 사용 가능)
//스트림 제대로 사용
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
freq = words
//수집기 사용
.collect(groupingBy(String::toLowerCase, counting()));
}
}
축소(스트림 원소를 객체 하나에 취합) 전략을 캡슐화한 블랙박스 객체
//빈도표에서 가장 흔한 단어 10개 뽑아내는 파이프라인
List<String> topTen = freq.keySet().stream()
.sorted(comparing(freq::get).reversed())
.limit(10)
.collect(toList());
Collectors의 나머지 36개 메서드들 중 대부분은 스트림을 맵으로 취합하는 기능으로, 진짜 컬렉션에 취합하는 것보다 복잡하다.
스트립의 각 원소는 키 하나와 값 하나에 연결되어 있으며 다수의 스트림 원소가 같은 키에 연관될 수 있다.