De acordo com a documentação no site da Oracle :
Os efeitos colaterais nos parâmetros comportamentais das operações de fluxo são, em geral, desencorajados, pois muitas vezes podem levar a violações involuntárias do requisito de apatridia, bem como a outros riscos de segurança da thread.
Isso inclui salvar elementos do fluxo em um banco de dados?
Imagine o seguinte código (pseudo):
public SavedCar saveCar(Car car) {
SavedCar savedCar = this.getDb().save(car);
return savedCar;
}
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
Quais são os efeitos indesejados opostos a esta implementação:
public SavedCar saveCar(Car car) {
SavedCar savedCar = this.getDb().save(car);
return savedCar;
}
public List<SavedCars> saveCars(List<Car> cars) {
List<SavedCars> savedCars = new ArrayList<>();
for (Cat car : cars) {
savedCars.add(this.saveCar(car));
}
return savedCars.
}
for
loop regular ?parallelStream
, certamente perderá o contexto da transação.Respostas:
Esse link é para o Java 8. Você pode ler a documentação para o Java 9 (lançado em 2017) e versões posteriores, pois elas são mais explícitas a esse respeito. Especificamente:
E também a versão atualizada do documento que você citou:
Toda ênfase é minha.
Como você pode ver, a documentação oficial atual entra em mais detalhes sobre os problemas que você pode encontrar se decidir usar efeitos colaterais em suas operações de fluxo. Também é muito claro
forEach
eforEachOrdered
são as únicas operações de terminal em que a execução de efeitos colaterais é garantida (lembre-se, os problemas de segurança de threads ainda se aplicam, como mostram os exemplos oficiais).Dito isto, e em relação ao seu código específico, e apenas ao código mencionado:
Não vejo problemas relacionados ao Streams com o código em questão.
.map()
etapa será executada porque.collect()
(uma operação de redução mutável , que é o que o documento oficial recomenda, em vez de outras coisas.forEach(list::add)
) depende.map()
da saída da e, como essa saída (ou sejasaveCar()
) é diferente da entrada, o fluxo não pode "provar" que [eleger] isso não afetaria o resultado do cálculo " .parallelStream()
por isso não deve apresentar nenhum problema de simultaneidade que não existia anteriormente (é claro, se alguém adicionar.parallel()
mais tarde, poderão surgir problemas - muito como se alguém decidisse paralelizar umfor
loop disparando novos threads para os cálculos internos )Isso não significa que o código nesse exemplo seja Good Code ™. A sequência
.stream.map(::someSideEffect()).collect()
como uma maneira de executar operações de efeitos colaterais para cada item de uma coleção pode parecer mais simples / curta / elegante? do que suafor
contraparte, e às vezes pode ser. No entanto, como Eugene, Holger e alguns outros lhe disseram, há maneiras melhores de abordar isso.Como um pensamento rápido: o custo de disparar um
Stream
iterativo versus um simplesfor
não é desprezível, a menos que você tenha muitos itens, e se você tiver muitos itens, então você: a) provavelmente não deseja fazer um novo acesso ao banco de dados para cada um, então umasaveAll(List items)
API seria melhor; eb) provavelmente não quer levar muito o desempenho ao processar muito de itens sequencialmente, para que você acabe usando paralelização e, em seguida, surja um novo conjunto de problemas.fonte
O exemplo mais fácil absoluto é:
Nesse caso, do java-9 e superior,
map
não será executado; já que você não precisa sabercount
nada.Existem outros casos múltiplos em que os efeitos colaterais causam muita dor; sob certas condições.
fonte
count()
que ainda seria executado, mas a implementação pode pular etapas intermediárias se puder produzir o resultado a partir da fonte (mas existem muitos ifs no nível de implementação )Collector
.work
nas versões antigas do Java 8, mas não no Java 9 ou posterior. Essa otimização específica para fluxos de tamanho foi introduzida no JDK-8067969