[java] Java 8에서 유형을 변환하는 Reduce 메소드에 결합기가 필요한 이유

combinerStreams reduce방식 에서 이행 하는 역할을 완전히 이해하는 데 어려움을 겪고 있습니다.

예를 들어 다음 코드는 컴파일되지 않습니다.

int length = asList("str1", "str2").stream()
            .reduce(0, (accumulatedInt, str) -> accumulatedInt + str.length());

컴파일 오류 :
(인수 불일치; int를 java.lang.String으로 변환 할 수 없음)

그러나이 코드는 컴파일합니다 :

int length = asList("str1", "str2").stream()
    .reduce(0, (accumulatedInt, str ) -> accumulatedInt + str.length(),
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2);

결합기 방법이 병렬 스트림에서 사용된다는 것을 이해합니다. 따라서 예제에서는 두 개의 중간 누적 정수를 더합니다.

그러나 첫 번째 예제가 결합기없이 컴파일되지 않는 이유 또는 결합기가 두 개의 정수를 더하기 때문에 문자열을 int로 변환하는 방법을 이해하지 못합니다.

누구든지 이것에 빛을 비출 수 있습니까?



답변

reduce사용하려고 시도한 2 ~ 3 개의 인수 버전이 에 동일한 유형을 허용하지 않습니다 accumulator.

두 인수 reduce다음과 같이 정의됩니다 .

T reduce(T identity,
         BinaryOperator<T> accumulator)

귀하의 경우 T는 문자열이므로 BinaryOperator<T>두 개의 문자열 인수를 허용하고 문자열을 반환해야합니다. 그러나 int와 String을 전달하면 컴파일 오류가 발생합니다 argument mismatch; int cannot be converted to java.lang.String. 실제로 String이 예상되기 때문에 (T) ID 값도 0이므로 0을 전달하는 것으로 생각합니다.

또한이 버전의 reduce는 Ts 스트림을 처리하고 T를 반환하므로 String 스트림을 int로 줄이는 데 사용할 수 없습니다.

세 가지 인수 reduce다음과 같이 정의됩니다 .

<U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner)

귀하의 경우 U는 정수이고 T는 문자열 이므로이 방법은 문자열 스트림을 정수로 줄입니다.

들어 BiFunction<U,? super T,U>누적 당신은 당신의 경우에 정수와 문자열을 두 개의 서로 다른 유형 (U와? 슈퍼 T)의 매개 변수를 전달할 수 있습니다. 또한 ID 값 U는 귀하의 경우 정수를 허용하므로 0을 전달하는 것이 좋습니다.

원하는 것을 달성하는 또 다른 방법 :

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .reduce(0, (accumulatedInt, len) -> accumulatedInt + len);

여기서 스트림 유형은의 반환 유형과 일치 reduce하므로의 두 매개 변수 버전을 사용할 수 있습니다 reduce.

물론 전혀 사용할 필요가 없습니다 reduce.

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .sum();


답변

, 둘다의 답변 의 2 및 3 ARG ARG 버전 차이 기재된 reduce전자가 감소 시킴에 Stream<T>T후자는 감소하는 반면 Stream<T>에이 U. 그러나 실제로로 축소 Stream<T>할 때 추가 결합기 기능의 필요성을 설명하지는 않았습니다 U.

Streams API의 디자인 원칙 중 하나는 API가 순차적 스트림과 병렬 스트림간에 다르지 않아야하거나 다른 방법으로 특정 스트림이 순차적으로 또는 병렬로 스트림이 올바르게 실행되는 것을 막지 않아야한다는 것입니다. 람다에 올바른 속성 (연관, 비 간섭 등)이있는 경우 순차적으로 또는 병렬로 실행되는 스트림은 동일한 결과를 제공해야합니다.

먼저 두 가지 버전의 축소를 고려해 보겠습니다.

T reduce(I, (T, T) -> T)

순차적 구현은 간단합니다. 항등 값 I은 0 번째 스트림 요소와 함께 “누적”되어 결과를 제공합니다. 이 결과는 제 1 스트림 요소와 함께 누적되어 다른 결과를 제공하며, 결과적으로 제 2 스트림 요소와 함께 누적되는 식으로 진행된다. 마지막 요소가 누적 된 후 최종 결과가 반환됩니다.

병렬 구현은 스트림을 세그먼트로 분할하여 시작합니다. 각 세그먼트는 위에서 설명한 순서대로 자체 스레드에 의해 처리됩니다. 이제 N 개의 스레드가 있으면 N 개의 중간 결과가 나타납니다. 이것들은 하나의 결과로 줄여야합니다. 각 중간 결과는 유형 T이고 여러 개의 결과가 있으므로 동일한 누산기 함수를 사용하여 N 개의 중간 결과를 단일 결과로 줄일 수 있습니다.

이제 감소 가상의 두 인수 감소 동작을 살펴 보자 Stream<T>에를 U. 다른 언어에서는 이것을 “접음” 또는 “왼쪽 접힘”조작이라고하므로 여기에이를 호출합니다. Java에는 존재하지 않습니다.

U foldLeft(I, (U, T) -> U)

ID 값 I은 U 유형입니다.

순차 버전은 중간 값이 T 유형 대신 U 유형이라는 점을 제외하고 foldLeft는 순차 버전과 reduce같습니다. 그러나 그렇지 않으면 동일합니다. (가설 foldRight작업은 왼쪽에서 오른쪽 대신 오른쪽에서 왼쪽으로 수행된다는 점을 제외하면 비슷합니다.)

이제의 병렬 버전을 고려하십시오 foldLeft. 스트림을 세그먼트로 분할하여 시작하겠습니다. 그런 다음 각 N 스레드가 세그먼트의 T 값을 U 유형의 N 중간 값으로 줄 이도록 할 수 있습니다. 이제 무엇? U 유형의 N 값에서 U 유형의 단일 결과까지 어떻게 얻습니까?

누락 된 것은 U 유형의 여러 중간 결과를 U 유형의 단일 결과로 결합 하는 또 다른 함수입니다. 두 개의 U 값을 하나로 결합하는 함수가 있으면 값을 하나로 줄이면 충분합니다. 위의 원래 축소. 따라서 다른 유형의 결과를 제공하는 축소 연산에는 두 가지 기능이 필요합니다.

U reduce(I, (U, T) -> U, (U, U) -> U)

또는 Java 구문을 사용하십시오.

<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

요약하면, 다른 결과 유형으로 병렬 축소를 수행하려면 T 요소를 중간 U 값으로 누적 하는 함수 와 중간 U 값을 단일 U 결과로 결합 하는 함수가 필요합니다 . 전환 유형이 아닌 경우 누산기 기능이 결합기 기능과 동일하다는 것이 밝혀졌습니다. 그렇기 때문에 같은 유형으로 줄이면 누산기 기능 만 있고 다른 유형으로 줄이면 별도의 누산기와 결합기 기능이 필요합니다.

마지막으로, 자바는 제공하지 않습니다 foldLeftfoldRight그들이 본질적으로 순차적 인 작업의 특정 순서를 의미하기 때문에 작업. 이는 순차적 및 병렬 작업을 동일하게 지원하는 API를 제공하는 위에서 언급 한 설계 원칙과 충돌합니다.


답변

개념을 명확하게하기 위해 낙서와 화살표를 좋아하기 때문에 시작합시다!

문자열에서 문자열로 (순차 스트림)

4 개의 문자열이 있다고 가정하십시오. 목표는 이러한 문자열을 하나로 연결하는 것입니다. 기본적으로 유형으로 시작하고 동일한 유형으로 완료합니다.

당신은 이것을 달성 할 수 있습니다

String res = Arrays.asList("one", "two","three","four")
        .stream()
        .reduce("",
                (accumulatedStr, str) -> accumulatedStr + str);  //accumulator

그리고 이것은 무슨 일이 일어나고 있는지 시각화하는 데 도움이됩니다.

여기에 이미지 설명을 입력하십시오

누산기 기능은 (빨간색) 스트림의 요소를 단계적으로 줄어든 최종 녹색 값으로 변환합니다. 누산기 함수는 단순히 String객체를 다른 객체로 변환합니다 String.

String에서 int로 (병렬 스트림)

동일한 4 개의 문자열이 있다고 가정합니다. 새로운 목표는 길이를 합산하고 스트림을 병렬화하려는 것입니다.

필요한 것은 다음과 같습니다.

int length = Arrays.asList("one", "two","three","four")
        .parallelStream()
        .reduce(0,
                (accumulatedInt, str) -> accumulatedInt + str.length(),                 //accumulator
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2); //combiner

그리고 이것은 일어나고있는 일의 계획입니다

여기에 이미지 설명을 입력하십시오

여기서 누산기 기능 (a BiFunction)을 사용하면 String데이터를 데이터로 변환 할 수 있습니다 int. 스트림이 평행하기 때문에 두 부분 (빨간색)으로 나뉘며, 각 부분은 서로 독립적으로 정교하게 만들어졌으며 부분적인 (주황색) 결과와 거의 같습니다. 부분 int결과를 최종 (녹색) 결과로 병합하기위한 규칙을 제공하려면 결합기를 정의해야합니다 int.

String에서 int (순차 스트림)

스트림을 병렬화하지 않으려면 어떻게해야합니까? 어쨌든 컴 바이 너를 제공해야하지만 부분 결과가 생성되지 않으면 호출되지 않습니다.


답변

병렬로 실행할 수 없으므로 결합기가 없는 두 가지 유형을 사용하는 축소 버전 은 없습니다 (이것이 왜 필요한지 잘 모르겠습니다). 누산기 가 연결되어야 한다는 사실 때문에 다음과 같은 이유로이 인터페이스를 거의 쓸모 없게 만듭니다.

list.stream().reduce(identity,
                     accumulator,
                     combiner);

다음과 같은 결과를 생성합니다.

list.stream().map(i -> accumulator(identity, i))
             .reduce(identity,
                     combiner);


답변