2015-01-16 2 views
2

Java 8에서 Stream 패키지 작업을 완전히 이해하려고 노력 중이며 도움이 필요합니다.Java 8 스트림을 사용하는 집계 정보

데이터베이스 호출의 일부로 목록에서 수신되는 클래스가 아래에 설명되어 있습니다.

class VisitSummary { 
    String source; 
    DateTime timestamp; 
    Integer errorCount; 
    Integer trafficCount; 
    //Other fields 
} 

이에 대한 몇 가지 가능성이 유용한 정보를 생성하기 위해, 나는 (주어진 기간 동안의) 모든 방문의 합계를 보유하는 클래스 VisitSummaryBySource 있습니다

class VisitSummaryBySource { 
    String sourceName; 
    Integer recordCount; 
    Integer errorCount; 
} 

나는 List<VisitSummaryBySource>를 구성하는 기대했다 컬렉션은 이름이 소리가 나면서 각기 다른 소스에 대해 발생한 레코드 및 오류의 총계를 포함하는 VisitSummaryBySource 개체의 목록을 보유합니다.

하나의 작업으로 스트림을 사용하여이를 수행 할 수있는 방법이 있습니까? 아니면 반드시 여러 작업으로 나누어야합니까? 내가 가지고 올 수있는 최선은 다음과 같습니다

Map<String, Integer> recordsBySrc = data.parallelStream().collect(Collectors.groupingBy(VisitSummaryBySource::getSource, 
        Collectors.summingInt(VisitSummaryBySource::getRecordCount))); 

및 계산 오류

Map<String, Integer> errorsBySrc = data.parallelStream().collect(Collectors.groupingBy(VisitSummaryBySource::getSource, 
        Collectors.summingInt(VisitSummaryBySource::getErrorCount))); 

와 내가 찾고 목록을 마련하기 위해 두 개의 맵을 병합.

+0

. 즉, 귀하의 코드 조각은 귀하의 질문에 표시되지 않은 메소드에 대한 메소드 참조를 사용합니다. 또한 원본 소스가'VisitSummary' 또는'VisitSummaryBySource'의'List'인지 여부는 명확하지 않습니다. 귀하의 코드는 후자를 제안하지만, 왜 우리에게 미사용 클래스'VisitSummary'를 보여 줬는 지 명확하지 않습니다 ... – Holger

+0

제 사과, 제 초기 게시물이 너무 길다는 사실을 깨닫고 난 후에 그 게시물을보다 간결하게하려고했습니다. 나는 코드를 자르거나 중요한 것을 남겨 두는 것보다 더 잘 알 것이다 :-) – Qrious

답변

1

당신은 올바른 길을 가고 있습니다. Collectors.summingInt의 용도는 다운 스트림 콜렉터 외부 예 : groupingBy 컬렉터입니다. 이 연산은 같은 그룹의 각 VisitSummaryBySource 인스턴스에서 정수 값 중 하나를 추출하여 합합니다. 이것은 본질적으로 정수보다 감소한 것입니다.

문제는 정수 값 중 하나만 추출/축소 할 수 있으므로 다른 정수 값을 추출하거나 줄이기 위해 두 번째 패스를 수행해야한다는 것입니다.

핵심은 개별 정수 값이 아니라 전체 VisitSummaryBySource 개체에 대한 축소를 고려하는 것입니다. 감축은 BinaryOperator으로, 해당 유형의 인스턴스 2 개를 취해 하나에 결합합니다. 여기 VisitSummaryBySource에 정적 메서드를 추가하여, 그렇게하는 방법은 다음과 같습니다 우리가 실제로 소스 이름을 병합하지 않을

static VisitSummaryBySource merge(VisitSummaryBySource a, 
            VisitSummaryBySource b) { 
    assert a.getSource().equals(b.getSource()); 
    return new VisitSummaryBySource(a.getSource(), 
            a.getRecordCount() + b.getRecordCount(), 
            a.getErrorCount() + b.getErrorCount()); 
} 

참고. 이 축소는 소스 이름이 같은 그룹 내에서만 수행되므로 이름이 같은 두 인스턴스 만 병합 할 수 있다고 주장합니다. 우리는 또한 명백한 생성자가 이름, 레코드 수 및 오류 수를 취하고 그 수를 합계로 포함하는 병합 된 객체를 생성한다고 가정합니다.

지금 우리의 스트림은 다음과 같습니다이 감소 유형 Optional<VisitSummaryBySource>의 맵 값을 생성하는 것을

Map<String, Optional<VisitSummaryBySource>> map = 
     data.stream() 
      .collect(groupingBy(VisitSummaryBySource::getSource, 
           reducing(VisitSummaryBySource::merge))); 

참고. 이것은 다소 이상합니다. 우리는 그것을 아래에서 다룰 것입니다. 신원 값을 취하는 reducing 수집기의 다른 형태를 사용하여 Optional을 피할 수 있습니다. 이것은 가능하지만 다소 신빙성이 없습니다. 신원의 소스 이름에 사용할 가치가 없기 때문입니다. 빈 문자열과 같은 것을 사용할 수는 있지만 원본 이름이 같은 개체 만 병합한다는 주장을 포기해야합니다.)

우리는지도를별로 신경 쓰지 않습니다. VisitSummaryBySource 인스턴스를 줄일 수있을만큼 길게 유지하면됩니다. 완료되면 values()을 사용하여지도 값을 빼고지도를 버릴 수 있습니다.

우리는 이것을 다시 스트림으로 바꿔 을 Optional::get을 통해 매핑하여 풀 수 있습니다. 적어도 하나의 그룹 구성원이 없으면 값이지도에서 끝나지 않으므로 안전합니다.

마지막으로 결과를 목록으로 수집합니다.

마지막 코드는 다음과 같습니다 : 당신은 우리가 현장에서 그들을 생각시키는 대신 클래스의 가능한 방법과 생성자를 포함해야

List<VisitSummaryBySource> output = 
     data.stream() 
      .collect(groupingBy(VisitSummaryBySource::getSource, 
           reducing(VisitSummaryBySource::merge))) 
      .values().stream() 
      .map(Optional::get) 
      .collect(toList()); 
+0

놀라운 설명을 해주셔서 감사합니다! – Qrious

+0

@Qrious 환영합니다! 그리고 받아 들여 주셔서 감사합니다. 나는 당신의 데이터 모델이 정확히 맞는지 확신 할 수 없다. 그러나 어떤 경우 든 내가 보여준 코드의 개념이 실제 모델로 적절하게 전달되기를 바랍니다. –