Estou tentando entender por que há uma diferença quando mudo de y.addAll(x) para x.addAll(y) no trecho de código abaixo:
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);
Eu sei que quando uso paraleloStream(), há mais de um thread sendo executado por vez.
collect() possui três parâmetros; os dois primeiros parâmetros eu entendo. Com o terceiro parâmetro, sei que x, y são substreams e são ArrayLists, mas não entendo por que os resultados são diferentes em cada caso. Eu esperava que eles fossem iguais.
(x, y) -> y.addAll(x) // saída: [1]
(x, y) -> x.addAll(y) // saída: [1, 2]
Por que um está correto e o outro não
Dos Javadocs de
Stream#collect
(especificamente o último parâmetro, ênfase minha):Da mesma forma,
a.addAll(b)
adiciona todos os elementos deb
to,a
mas não o contrário. Ele pega informações do parâmetro e modifica o receptor.Portanto, o contrato desse método especifica que você deve mesclar o segundo argumento do lambda com o primeiro.
Se você fizer isso
(x, y) -> x.addAll(y)
, todos os elementos serão adicionadosy
àx
adesão ao contrato. No entanto,(x, y) -> y.addAll(x)
você está adicionando-o ao segundo elemento, resultando nay
não adição de elementos aosx
quais faltam no resultado.O que acontece
Isso é feito dessa maneira porque os fluxos paralelos dividem o processamento em partes, onde diferentes threads processam partes diferentes. Após o processamento, é necessário mesclar os elementos, o que é feito usando o combinador (a última expressão lambda que você falou). Este combinador precisa ser capaz de combinar dois elementos e o primeiro argumento é então usado para processamento adicional enquanto o segundo é descartado.
Digamos que temos os números 1 e 2 como no seu exemplo e assumimos que um thread processa um pedaço contendo 1 e o outro thread processa um pedaço contendo 2. Ao coletar, cada thread começa criando um novo
ArrayList
noArrayList::new
seu código. Os threads então adicionam os elementos de seus pedaços correspondentes à lista, resultando em duas listas com um elemento cada (1 para o primeiro thread e 2 para o outro). Quando ambos os threads terminam, o combinador é chamado para mesclar/combinar os resultados. Comx.addAll(y)
, adiciona a segunda lista à primeira, que é então retornada produzindo o resultado correto. No entanto, comy.addAll(x)
, ele adiciona os elementos da primeira lista à segunda lista, mas Java assume que você deseja a primeira lista (pois é isso que você deve modificar), então collect retorna a primeira lista que não contém os elementos processados pelo segundo tópico.Renomeei algumas de suas variáveis para ilustrar um pouco melhor o que está acontecendo.
O método
Stream::collect
retornathisList
.Quando você altera o terceiro parâmetro da chamada para
collect()
assim:você adiciona o conteúdo de
thisList
totheOtherList
ethisList
permanece inalterado, o que significa que o conteúdo detheOtherList
não é adicionado a ele.Que o resultado seja justo
1
é coincidência.