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


142

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로 변환하는 방법을 이해하지 못합니다.

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



2
아하, 그것은 병렬 스트림을위한 것입니다 ... 나는 누출 추상화라고 부릅니다!
Andy

답변:


77

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();

8
마지막 코드의 두 번째 옵션으로 mapToInt(String::length)over 를 사용할 수 있습니다. mapToInt(s -> s.length())하나가 다른 것보다 나을지 확실하지 않지만 가독성을 위해 전자를 선호합니다.
skiwi

20
많은 사람들은 왜 combiner필요한지, 왜 없는지 accumulator충분한 지 알지 못하므로이 답을 찾을 것 입니다. 이 경우 : 결합기는 스레드의 "누적 된"결과를 결합하기 위해 병렬 스트림에만 필요합니다.
ddekany

1
나는 당신의 대답이 특히 유용하다고 생각하지 않습니다-당신은 결합 기가해야 할 일과 그것을하지 않고 어떻게 일할 수 있는지 전혀 설명하지 않기 때문에! 제 경우에는 유형 T를 U로 줄이고 싶지만 전혀 병렬로 수행 할 수있는 방법이 없습니다. 단순히 불가능합니다. 시스템에 병렬 처리를 원하지 않는다고 말하고 결합기를 제외시키는 방법은 무엇입니까?
Zordid

@Zordid the Streams API에는 결합기를 전달하지 않고 유형 T를 U로 줄이는 옵션이 없습니다.
Eran

216

, 둘다의 답변 의 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를 제공하는 위에서 언급 한 설계 원칙과 충돌합니다.


7
foldLeft계산이 이전 결과에 의존하고 병렬화 할 수 없기 때문에 필요한 경우 어떻게 할 수 있습니까?
amoebe

5
@amoebe를 사용하여 자신의 foldLeft를 구현할 수 있습니다 forEachOrdered. 그러나 중간 상태는 캡처 된 변수로 유지되어야합니다.
스튜어트 마크

@StuartMarks 덕분에 jOOλ를 사용했습니다. 그들은 깔끔한 구현을했습니다foldLeft .
amoebe

1
이 답변을 사랑하십시오! 내가 틀렸다면 정정하십시오. 이것은 OP의 실행 예제 (두 번째 예제)가 실행시 스트림 순차 인 결합기를 호출하지 않는 이유를 설명합니다.
루이지 코르테스

2
그것은 거의 모든 것을 설명합니다 ... 단, 이것이 순차적으로 축소를 배제 해야하는 이유는 무엇입니까? 필자의 경우 축소는 선행 작업 결과의 중간 결과에서 각 함수를 호출하여 함수 목록을 U로 줄이므로 병렬로 수행하는 것은 불가능합니다. 이것은 병렬로 수행 할 수 없으며 결합기를 설명 할 방법이 없습니다. 이를 위해 어떤 방법을 사용할 수 있습니까?
Zordid

116

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

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

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 (순차 스트림)

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


7
고마워 읽을 필요조차 없었습니다. 나는 그들이 끔찍한 접기 기능을 추가했으면 좋겠다.
Lodewijk Bogaards 2014 년

1
@LodewijkBogaards 도와 주셔서 감사합니다! 여기 JavaDoc 은 꽤 비밀 스럽습니다
Luigi Cortese

@LuigiCortese 병렬 스트림에서 항상 요소를 쌍으로 나누나요?
TheLogicGuy

1
귀하의 명확하고 유용한 답변에 감사드립니다. "음, 어쨌든 결합기를 제공해야하지만 결코 호출되지는 않습니다." 이것은 "용감한 코드를보다 간결하고 읽기 쉽게 만들어주는"무수한 시간을 보장 한 Brave New Java 기능 프로그래밍의 일부입니다. 이와 같은 (손가락 인용) 간결한 명확성의 예가 거의 남아 있지 않기를 바랍니다.
dnuttle

여덟 줄로 줄이기를 설명하는 것이 훨씬 나을 것입니다 ...
Ekaterina Ivanova iceja.net

0

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

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

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

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

이러한 map트릭은 특정에 따라 accumulator그리고 combiner꽤 많은 것들을 속도가 느려질 수 있습니다.
Tagir Valeev

또는 accumulator첫 번째 매개 변수를 삭제하여 단순화 할 수 있으므로 속도를 크게 향상 시키십시오 .
quiz123

병렬 축소가 가능하며 계산에 따라 다릅니다. 귀하의 경우 결합기의 복잡성뿐만 아니라 다른 인스턴스와 비교하여 신원에 대한 누산기도 알고 있어야합니다.
LoganMzz 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.