2017-03-23 10 views
5

감소 : Elegantly create map with object fields as key/value from object stream in Java 8합니다. 요약 목표는 연령과 키를 가진지도를 얻는 것이었고 사람의 취미는 Set입니다.자바 8 stream.collect (... groupingBy (... 매핑 (... 감소))) 내가 <code>groupingBy</code>, <code>mapping</code> 다음과 같은 질문에 <code>reducing</code> 사용하여 솔루션을 놀았 BinaryOperator-사용

내가 생각해 낸 해결책 중 하나 (이상한 것은 아니지만 그게 중요한 것이 아닙니다)에는 이상한 행동이있었습니다. 입력으로 다음 목록으로

:

List<Person> personList = Arrays.asList(
    new Person(/* name */ "A", /* age */ 23, /* hobbies */ asList("a")), 
    new Person("BC", 24, asList("b", "c")), 
    new Person("D", 23, asList("d")), 
    new Person("E", 23, asList("e")) 
); 

하고 다음 솔루션 :

Collector<List<String>, ?, Set<String>> listToSetReducer = Collectors.reducing(new HashSet<>(), HashSet::new, (strings, strings2) -> { 
    strings.addAll(strings2); 
    return strings; 
}); 
Map<Integer, Set<String>> map = personList.stream() 
              .collect(Collectors.groupingBy(o -> o.age, 
                     Collectors.mapping(o -> o.hobbies, listToSetReducer))); 
System.out.println("map = " + map); 

내가 가진 :

map = {23=[a, b, c, d, e], 24=[a, b, c, d, e]} 

분명 내가 기대했다되지 것을. 난 그냥 내가 예상 결과를 얻을 (strings2, strings)에 (환원 컬렉터의) 바이너리 연산자의 (strings, strings2)의 순서를 교체 할 경우 지금

map = {23=[a, d, e], 24=[b, c]} 

: 차라리이 예상. 그래서 나는 무엇을 여기에서 놓쳤 느냐? reducing- 콜렉터를 잘못 해석 했습니까? 또는 어떤 문서를 그리워 했는가? 내 사용법이 예상대로 작동하지 않는다는 것을 분명하게 알 수 있습니까?

자바 버전은 1.8.0_121입니다.

답변

9

줄이기은 들어오는 객체를 수정해서는 안됩니다. 귀하의 경우 신원 값으로 간주되는 들어오는 HashSet을 수정하여 모든 그룹이 모든 값을 포함하는 결과로 동일한 HashSet 인스턴스를 갖게됩니다.

는 당신이 필요로하는 것은

Map<Integer, Set<String>> map = personList.stream() 
    .collect(Collectors.groupingBy(o -> o.age, 
     Collector.of(HashSet::new, (s,p) -> s.addAll(p.hobbies), (s1,s2) -> { 
      s1.addAll(s2); 
      return s1; 
     }))); 

이유는, 우리는 모든 사용자 지정 수집기를 필요 등이 이미 미리 만들어진 수집 Collectors.toList(), Collectors.toSet()로 구현 된 것처럼 Collector.of(…)를 통해 구현 될 수있는 Mutable Reduction,이다 , Java 8에는 flatMapping 콜렉터가 없다는 것입니다.이 콜렉터는 Java 9에서 소개 할 예정입니다. 그것으로,이 솔루션은 보이는 것 같은 :

Map<Integer, Set<String>> map = personList.stream() 
    .collect(Collectors.groupingBy(o -> o.age, 
     Collectors.flatMapping(p -> p.hobbies.stream(), Collectors.toSet()))); 
+0

재미있는 .. 심지어 Collectors''에서 flatMapping''찾기 (또는'hobbies.stream를 (적용하기) '과 함께 작동하도록) 시도 처음에는 . 좋은 소식입니다. 오 이런, 나는 결합자를 오퍼레이터와 섞었다. 따라서 매개 변수를 전환하면 올바른 결과가 도출되는 것은 우연한 일입니다. 귀하의 설명에 감사드립니다! – Roland

+2

예, 순차적 인 컨텍스트에서 감소 함수는 항상 'f (이전, 다음)'로 평가됩니다. 반면 '이전'은 첫 번째 평가의 ID 값이고 이후 평가의 이전 결과입니다. 그러므로 (a, b) -> a는 항등 값으로 끝나고, (a, b) -> b는 매퍼 함수에 의해 생성 된 신선한 값을 사용하게됩니다. 그러나 병렬 평가에서 두 인수는 이전 부분 평가의 결과 일 수 있으며 부분 결과가 비어있을 수 있으므로 두 인수 중 하나가 신원 값이 될 수 있으므로 두 번째 인수를 사용하는 것이 신뢰할 수있는 수정이 아닙니다. – Holger