Conforme a documentação no site da Oracle [...]
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:
Uma implementação de fluxo é permitida latitude significativa na otimização do cálculo do resultado. Por exemplo, uma implementação de fluxo é livre para eliminar operações (ou estágios inteiros) de um pipeline de fluxo - e, portanto, eliminar a invocação de parâmetros comportamentais - se puder provar que isso não afetaria o resultado da computação. Isso significa que os efeitos colaterais dos parâmetros comportamentais nem sempre podem ser executados e não devem ser invocados, a menos que seja especificado de outra forma (como nas operações do terminal forEach
e forEachOrdered
). (Para um exemplo específico dessa otimização, consulte a nota da API documentada na count()
operação. Para obter mais detalhes, consulte a seção de efeitos colaterais da documentação do pacote de fluxo.)
Fonte: Javadoc do Java 9 para a Stream
interface .
E também a versão atualizada do documento que você citou:
Efeitos colaterais
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, além de outros riscos à segurança do encadeamento.
Se os parâmetros comportamentais tiverem efeitos colaterais, a menos que declarado explicitamente, não há garantias quanto a :
- a visibilidade desses efeitos colaterais para outros threads;
- que operações diferentes no "mesmo" elemento dentro do mesmo pipeline de fluxo são executadas no mesmo encadeamento; e
- que os parâmetros comportamentais são sempre invocados, uma vez que uma implementação de fluxo é livre para eliminar operações (ou estágios inteiros) de um pipeline de fluxo, se for possível provar que isso não afetaria o resultado da computação.
A ordem dos efeitos colaterais pode ser surpreendente. Mesmo quando um pipeline é restrito a produzir um resultado consistente com a ordem de encontro da origem do fluxo (por exemplo, IntStream.range(0,5).parallel().map(x -> x*2).toArray()
deve produzir [0, 2, 4, 6, 8]
), não há garantias quanto à ordem na qual a função do mapeador é aplicada a elementos individuais ou em qual thread qualquer parâmetro comportamental é executado para um determinado elemento.
A eliminação de efeitos colaterais também pode ser surpreendente. Com exceção das operações do terminal forEach
eforEachOrdered
, os efeitos colaterais dos parâmetros comportamentais nem sempre podem ser executados quando a implementação do fluxo pode otimizar a execução dos parâmetros comportamentais sem afetar o resultado da computação. (Para um exemplo específico, consulte a nota da API documentada na count
operação.)
Fonte: Javadoc do Java 9 para o java.util.stream
pacote .
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
e forEachOrdered
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:
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
Não vejo problemas relacionados ao Streams com o código em questão.
- A
.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 seja saveCar()
) é diferente da entrada, o fluxo não pode "provar" que [eleger] isso não afetaria o resultado do cálculo " .
- Não é um problema,
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 um for
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 sua for
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 simples for
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 uma saveAll(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.