결과모으기
목차
1. 결과 모으기
forEach 메서드를 사용해서 각 요소에 함수를 적용할 수 있다.
String[] contents = { "lumberjack", "rock on", "food", "for", "all" };
List<String> wordList = Arrays.asList(contents);
Stream<String> words = wordList.stream();
words.forEach(System.out::println);
/*
lumberjack
rock on
food
for
all
*/
병렬 스트림에서는 forEach 메서드는 요소를 임의의 순서로 호출한다. 스트림 순서로 처리하려면 forEach 대신 forEachOrdered 메서드를 호출하면 되지만 이럴 경우 병렬성이 주는 이점을 거의 포기해야 한다.
대부문의 경우는 결과를 자료구조로 모으려고 할 것이다. toArray를 호출하면 스트림 요소의 배열을 얻을 수 있다.
런타임에 제네릭 배열을 생성할 수 없으므로 stream.toArray()는 Object[]반환한다. 올바른 타입의 배열이 필요하면 배열생성자를 전달한다.(String[]::new)
String[] result = words.toArray(String[]::new);
for(String r : result) {
System.out.println(r);
}
/*
lumberjack
rock on
food
for
all
*/
스트림 요소를 또 다른 타킷으로 모으는 데는 collect 메서드를 이용하면 편하다. collect 메서드는 Collector 인터페이스의 인스턴스를 받는다. Collectors 클래스에는 공통 컬렉터용 팩ㄹ토리 메서드가 여러 개 있다. 스트림을 리스트로 모을 때는 다음과 같이 간단히 호출하면 된다.
// 리스트로 모을 때
List<String> myList = words.collect(Collectors.toList());
// 집합으로 모을 때
Set<String> mySet = words.collect(Collectors.toSet());
// 어떤 집합 종류를 얻을지 제어하고 싶을 때
TreeSet<String> myTreeSet = words.collect(Collectors.toCollection(TreeSet::new));
스트림에 있는 모든 문자열을 서로 연결해서 모을 때
String myjoining = words.collect(Collectors.joining());
System.out.println(myjoining);
// lumberjackrock onfoodforall
요소 간에 구분자가 필요한 경우 해당 구분자를 joining 메서드에 전달한다.
String seperater = words.collect(Collectors.joining(", "));
System.out.println(seperater);
// lumberjack, rock on, food, for, all
스트림에 문자열 외의 객체가 포함되면 먼저 해당 객체를 문자열로 변환해야 한다.
String result = stream.map(Object::toString).collect(Collectors.joining(", "));
스트림 결과를 합계, 평균, 최댓값, 최솟값으로 리듀스하려면 summarizing(Int Long Double) 메서드 중 하나를 사용해야 한다. 이 메서드들은 스트림 객체를 숫자로 매핑하는 함수를 받고 합계, 평균, 최댓값, 최솟값을 동시에 계산해서 (Int Long Double)SummaryStatistics 타입 결과를 돌려준다.
IntSummaryStatistics summary = noVowels("alice.txt").collect(
Collectors.summarizingInt(String::length));
double averageWordLength = summary.getAverage();
double maxWordLength = summary.getMax();
2. 맵으로 모으기
Collectors.toMap 메서드는 두 함수의 인자를 받고, 각각 맵의 키와 값을 만들어낸다.
public static Stream<Person> people() {
return Stream.of(new Person(1001, "Peter"), new Person(1002, "Paul"), new Person(1003, "Mary"));
}
public static void main(String[] args) {
Map<Integer, String> idToName = people().collect(Collectors.toMap(Person::getId, Person::getName));
System.out.println("idToName: " + idToName);
}
// idToName: {1001=Peter, 1002=Paul, 1003=Mary}
값이 실제 요소여야 하는 경우에는 두 번째 함수로 Function.identity()를 사용한다.
Map<Integer, Person> idToPerson = people().collect(Collectors.toMap(Person::getId, Function.identity()));
// idToPerson: {1001=me.seungui.ch08.CollectingIntoMaps$Person[id=1001,name=Peter], 1002=me.seungui.ch08.CollectingIntoMaps$Person[id=1002,name=Paul], 1003=me.seungui.ch08.CollectingIntoMaps$Person[id=1003,name=Mary]}
키가 같은 요소가 두 개 이상이면 충돌이 발생한다. 이때 컬렉터는 IllegalStateException을 던진다. 이 동작은 새 값을 받아 충돌을 해결하고, 세 번째 함수의 인자를 제공해서 오버라이드할 수 있다. 세 번째 함수의 인자는 키에 해당하는 값을 결정한다. 이 함수는 기존 값, 새 값, 두 값을 조합을 반환할 수 있다.
여기서는 키를 이용해 각 언어별로 기본 로케일의 언어 이름이 포함된 맵을 생성한다. 각 언어는 사용 가능한 로케일에 있어야 한다. 그리고 값으로는 지역화된 이름(예를 들면 “Deutsch”)이 포함된다.
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String, String> languageNames = locales.collect(Collectors.toMap(
Locale::getDisplayLanguage,
Locale::getDisplayLanguage,
(existingValue, newValue) -> existingValue));
System.out.println("languageNames: " + languageNames);