Я пытаюсь понять, почему происходит другое, когда я меняю с y.addAll(x)
на x.addAll(y)
в фрагменте кода ниже:
List<Integer> result = List.of(1, 2)
.parallelStream()
.collect(
ArrayList::new,
(x, y) -> x.add(y),
(x, y) -> y.addAll(x)
);
System.out.println(result);
Я знаю, что когда я использую parallelStream
, одновременно выполняется более одного потока.
collect
имеет три параметра; первые два параметра я понимаю. Что касается третьего параметра, я знаю, что x, y — это подпотоки и они ArrayList
, но я не понимаю, почему результаты в каждом случае разные. Я ожидал, что они будут одинаковыми.
(x, y) -> y.addAll(x) // output: [1]
(x, y) -> x.addAll(y) // output: [1, 2]
потрясающе, спасибо
Из Javadocs Stream#collect (в частности, последний параметр, выделено мной):
Комбайнер — ассоциативная, неинтерферирующая функция без сохранения состояния, которая принимает два контейнера частичных результатов и объединяет их, что должно быть совместимо с функцией аккумулятора. Функция объединителя должна сложить элементы из второго контейнера результатов в первый контейнер результатов.
Аналогично, a.addAll(b) добавляет все элементы от b
до a
, но не наоборот. Он берет информацию из параметра и модифицирует приемник.
Итак, контракт этого метода указывает, что вам необходимо объединить второй аргумент лямбды с первым.
Если вы это сделаете (x, y) -> x.addAll(y)
, все элементы y
будут добавлены в x
в соответствии с договором. Однако с помощью (x, y) -> y.addAll(x)
вы добавляете его ко второму элементу, в результате чего элементы y
не добавляются к x
, а затем отсутствуют в результате.
Это сделано таким образом, потому что параллельные потоки разделяют обработку на фрагменты, где разные потоки обрабатывают разные фрагменты. После обработки необходимо объединить элементы вместе, что делается с помощью объединителя (последнее лямбда-выражение, о котором вы говорили). Этот объединитель должен иметь возможность объединять два элемента вместе, и затем первый аргумент используется для дальнейшей обработки, а второй отбрасывается.
Допустим, у нас есть числа 1 и 2, как в вашем примере, и предположим, что один поток обрабатывает фрагмент, содержащий 1, а другой поток обрабатывает фрагмент, содержащий 2. При сборе каждый поток начинается с создания нового ArrayList
после ArrayList::new
в вашем коде. Затем потоки добавляют элементы соответствующих фрагментов в список, в результате чего получается два списка по одному элементу в каждом (1 для первого потока и 2 для другого). Когда оба потока завершают работу, вызывается объединитель для слияния/объединения результатов. С помощью x.addAll(y)
он добавляет второй список к первому, который затем возвращается с правильным результатом. Однако с помощью y.addAll(x)
элементы первого списка добавляются во второй список, но Java предполагает, что вам нужен первый список (поскольку это то, что вы должны изменить), поэтому сбор возвращает первый список, который не содержит элементов, обработанных вторая ветка.
ответ идеальный: я знаю, что должен следовать контракту. Я хочу узнать больше об этом: «Вы добавляете его ко второму элементу, в результате чего элементы y не добавляются к x, а затем отсутствуют в результате». почему нам нужно заботиться о добавлении к x или добавлении к y?
@happy, потому что collect()
вернет первый параметр, то есть x
-- добавление к y
не изменит результат (его можно было запрограммировать на возврат результата последнего параметра, но это не сработает, например, с addAll()
поскольку он возвращает логическое значение)
@happy Способ реализации потока: он сохраняет x
(после добавления в него каких-либо элементов) и отбрасывает y
. Добавив все элементы x
в y
, поток сохранит исходный x
без каких-либо элементов y
.
Я добавил дополнительную информацию об этом в свой ответ. Надеюсь, это прояснит ситуацию. В контракте указано, что вы должны аккумулировать его в первом аргументе, чтобы он просто игнорировал второй.
Чтобы уточнить, в вашем случае это выглядит так: x
было [1]
и y
было [2]
. После добавления 1
к y
x
по-прежнему остается [1]
, то есть значение, которое использует и возвращает реализация потока, и, следовательно, это общий результат.
идеальный. Спасибо всем
Обратите внимание, что это отличается от определения Collector
, где вы можете делать с параметрами все, что захотите, при условии, что вы возвращаете их комбинацию.
@shmosel вы имеете в виду: разницу между объединителем в Collect() Stream и объединителем в Collector.class
Я переименовал некоторые из ваших переменных, чтобы лучше проиллюстрировать, что происходит.
List<Integer> result = List.of( 1, 2 )
.parallelStream()
.collect(
ArrayList::new,
(thisList,value) -> thisList.add( value ),
(thisList,theOtherList) -> thisList.addAll( theOtherList )
);
System.out.println( result );
Метод Stream::collect
возвращает thisList
.
Когда вы измените третий параметр вызова на collect()
вот так:
…
(thisList,theOtherList) -> theOtherList.addAll( thisList )
…
вы добавляете содержимое thisList
к theOtherList
, а thisList
остается неизменным, то есть содержимое theOtherList
к нему не добавляется.
То, что результат всего лишь 1
, случайно.
К вашему сведению, вы можете заменить
List::add
на(x, y) -> x.add(y)
. То же самое и сaddAll
.