• 마지막으로, reducing 메서드들은 다운스트림 요소들에 범용 리덕션을 적용한다. 세 가지 메서드 형태 reducing(binaryOperator), reducing(identity, binaryOperator), reducing(identity, mapper, binaryOperator)가 있다. 첫 번째 형태에서는 항등값이 null이다(항등 파라미터가 없으면 Optional 결과를 돌려주는 Stream::reduce의 형태와는 다르다는 점을 주목하기 바란다). 세 번째 형태에서는 mapper 함수가 적용되고 이 함수의 값이 리듀스된다.
다음은 각 주에 있는 모든 도시의 이름을 콤마 분리 문자열로 얻는 예제다. 여기서는 각 도시를 이름으로 맵핑해서 이름들을 연결한다.
Map<String, String> stateToCityNames = cities.collect(
groupingBy(City::getState,
reducing("", City::getName,
(s, t) -> s.length() == 0 ? t : s + ", " + t)));
Stream.reduce와 마찬가지로, Collectors.reducing은 거의 사용할 필요가 없다. 이 예제에서는 다음과 같은 코드로 같은 결과를 좀 더 자연스럽게 얻을 수 있다.
Map<String, String> stateToCityNames = cities.collect(
groupingBy(City::getState,
mapping(City::getName,
joining(", "))));
솔직히 다운스트림 컬렉터는 아주 난해한 표현식을 야기할 수 있다. 따라서 ‘다운스트림’ 맵 값들을 처리하기 위해 반드시 groupingBy 또는 partitioningBy와 연계해서 사용해야 한다. 그렇지 않으면, 단순히 map, reduce, count, max, min 같은 메서드를 스트림에 직접 적용한다.