Suponha que eu tenha um método que retorne uma exibição somente leitura em uma lista de membros:
class Team {
private List < Player > players = new ArrayList < > ();
// ...
public List < Player > getPlayers() {
return Collections.unmodifiableList(players);
}
}
Suponha ainda que tudo o que o cliente faz é iterar sobre a lista uma vez, imediatamente. Talvez colocar os jogadores em uma JList ou algo assim. O cliente não armazena uma referência à lista para inspeção posterior!
Dado esse cenário comum, devo retornar um fluxo?
public Stream < Player > getPlayers() {
return players.stream();
}
Ou o retorno de um fluxo não é idiomático em Java? Os fluxos foram projetados para sempre serem "finalizados" dentro da mesma expressão em que foram criados?
java
collections
java-8
encapsulation
java-stream
fredoverflow
fonte
fonte
players.stream()
é exatamente esse método que retorna um fluxo para o chamador. A verdadeira questão é: você realmente deseja restringir o chamador a uma travessia única e também negar a ele o acesso à sua coleção pelaCollection
API? Talvez o interlocutor apenas queira fazeraddAll
isso para outra coleção?Respostas:
A resposta é, como sempre, "depende". Depende do tamanho da coleção retornada. Depende se o resultado muda ao longo do tempo e qual a importância da consistência do resultado retornado. E isso depende muito de como o usuário provavelmente usará a resposta.
Primeiro, observe que você sempre pode obter uma coleção de um fluxo e vice-versa:
Portanto, a questão é: o que é mais útil para os chamadores?
Se o resultado for infinito, existe apenas uma opção: Stream.
Se o resultado for muito grande, você provavelmente prefere o Stream, pois pode não haver nenhum valor em materializá-lo de uma só vez, e isso pode criar uma pressão significativa no heap.
Se tudo o que o chamador fizer é iterar através dele (pesquisar, filtrar, agregar), você deve preferir o Stream, já que o Stream já os possui e não há necessidade de materializar uma coleção (especialmente se o usuário não puder processar o resultado completo.) Este é um caso muito comum.
Mesmo que você saiba que o usuário irá iterá-lo várias vezes ou mantê-lo por perto, você ainda pode retornar um Stream, pelo simples fato de que qualquer coleção que você escolher colocar (por exemplo, ArrayList) pode não ser a eles desejam e, em seguida, o chamador deve copiá-lo de qualquer maneira. se você retornar um fluxo, ele pode fazer
collect(toCollection(factory))
e obtê-lo exatamente da forma que deseja.Os casos acima "preferem Stream" derivam principalmente do fato de o Stream ser mais flexível; você pode vincular tarde a como usá-lo sem incorrer nos custos e restrições de materializá-lo em uma coleção.
O único caso em que você deve devolver uma coleção é quando existem requisitos de consistência fortes e é necessário produzir uma captura instantânea consistente de um destino em movimento. Então, você desejará colocar os elementos em uma coleção que não será alterada.
Então, eu diria que na maioria das vezes, o Stream é a resposta certa - é mais flexível, não impõe custos de materialização geralmente desnecessários e pode ser facilmente transformado na coleção de sua escolha, se necessário. Mas, às vezes, talvez você precise devolver uma coleção (por exemplo, devido a fortes requisitos de consistência), ou talvez queira devolver a coleção porque sabe como o usuário a usará e sabe que isso é a coisa mais conveniente para eles.
fonte
Tenho alguns pontos a acrescentar à excelente resposta de Brian Goetz .
É bastante comum retornar um fluxo de uma chamada de método no estilo "getter". Consulte a página de uso do Stream no javadoc do Java 8 e procure "métodos ... que retornam o Stream" para outros pacotes que não
java.util.Stream
. Esses métodos geralmente são em classes que representam ou podem conter vários valores ou agregações de algo. Nesses casos, as APIs normalmente retornam coleções ou matrizes delas. Por todas as razões que Brian observou em sua resposta, é muito flexível adicionar métodos de retorno de fluxo aqui. Muitas dessas classes já têm métodos de retorno de coleções ou matrizes, porque as classes são anteriores à API do Streams. Se você estiver projetando uma nova API e fizer sentido fornecer métodos de retorno de fluxo, talvez não seja necessário adicionar métodos de retorno de coleção também.Brian mencionou o custo de "materializar" os valores em uma coleção. Para ampliar esse ponto, existem realmente dois custos aqui: o custo de armazenar valores na coleção (alocação e cópia de memória) e também o custo de criar os valores em primeiro lugar. O último custo geralmente pode ser reduzido ou evitado tirando proveito do comportamento de busca de preguiça de um Stream. Um bom exemplo disso são as APIs em
java.nio.file.Files
:Não apenas
readAllLines
precisa armazenar todo o conteúdo do arquivo na memória para armazená-lo na lista de resultados, mas também ler o arquivo até o final antes de retornar a lista. Olines
método pode retornar quase imediatamente após a execução de alguma configuração, deixando a leitura do arquivo e a quebra de linha até mais tarde quando for necessário - ou não for o caso. Esse é um grande benefício, se, por exemplo, o chamador estiver interessado apenas nas dez primeiras linhas:É claro que pode ser economizado um espaço considerável na memória se o chamador filtrar o fluxo para retornar apenas linhas correspondentes a um padrão, etc.
Um idioma que parece estar surgindo é nomear métodos de retorno de fluxo após o plural do nome das coisas que ele representa ou contém, sem um
get
prefixo. Além disso, emborastream()
seja um nome razoável para um método de retorno de fluxo quando houver apenas um conjunto possível de valores a serem retornados, às vezes há classes que possuem agregações de vários tipos de valores. Por exemplo, suponha que você tenha algum objeto que contenha atributos e elementos. Você pode fornecer duas APIs de retorno de fluxo:fonte
É assim que eles são usados na maioria dos exemplos.
Nota: retornar um fluxo não é tão diferente de retornar um iterador (admitido com muito mais poder expressivo)
IMHO, a melhor solução é encapsular por que você está fazendo isso e não devolver a coleção.
por exemplo
ou se você pretende contá-los
fonte
Se o fluxo for finito e houver uma operação normal / esperada nos objetos retornados que gerará uma exceção verificada, sempre retornarei uma coleção. Porque se você estiver fazendo algo em cada um dos objetos que pode lançar uma exceção de verificação, você odiará o fluxo. Uma falta real de fluxos é a incapacidade de lidar com exceções verificadas de maneira elegante.
Agora, talvez isso seja um sinal de que você não precisa das exceções verificadas, o que é justo, mas às vezes elas são inevitáveis.
fonte
Ao contrário das coleções, os fluxos têm características adicionais . Um fluxo retornado por qualquer método pode ser:
Essas diferenças também existem nas coleções, mas são parte do contrato óbvio:
Como consumidor de um fluxo (a partir de um retorno de método ou como um parâmetro de método), essa é uma situação perigosa e confusa. Para garantir que seu algoritmo se comporte corretamente, os consumidores de fluxos precisam garantir que o algoritmo não faça suposições erradas sobre as características do fluxo. E isso é uma coisa muito difícil de fazer. Nos testes de unidade, isso significa que você deve multiplicar todos os seus testes para serem repetidos com o mesmo conteúdo de fluxo, mas com fluxos que sejam
Protetores de método de escrita para fluxos que lançam uma IllegalArgumentException se o fluxo de entrada tiver características que quebram o algoritmo, é difícil, porque as propriedades estão ocultas.
Isso deixa o Stream apenas como uma opção válida em uma assinatura de método quando nenhum dos problemas acima importa, o que raramente é o caso.
É muito mais seguro usar outros tipos de dados nas assinaturas de métodos com um contrato explícito (e sem o processamento implícito do conjunto de encadeamentos) que impossibilita o processamento acidental de dados com suposições erradas sobre ordem, tamanho ou paralelismo (e uso do conjunto de encadeamentos).
fonte
Eu acho que depende do seu cenário. Pode ser que, se você fizer seu
Team
implementoIterable<Player>
, é suficiente.ou no estilo funcional:
Mas se você quiser uma API mais completa e fluente, um fluxo pode ser uma boa solução.
fonte
stream.count()
é muito fácil), esse número não é realmente muito significativo para outra coisa senão a depuração ou estimativa.Enquanto alguns dos entrevistados mais destacados deram ótimos conselhos gerais, estou surpreso que ninguém tenha afirmado:
Se você já possui um "materializado"
Collection
em mãos (ou seja, ele já foi criado antes da chamada - como é o caso no exemplo dado, onde é um campo de membro), não faz sentido convertê-lo para aStream
. O chamador pode facilmente fazer isso sozinho. Visto que, se o chamador quiser consumir os dados em sua forma original, você os converterá em umStream
que os obriga a fazer um trabalho redundante para rematerializar uma cópia da estrutura original.fonte
fonte
Eu provavelmente teria 2 métodos, um para retornar um
Collection
e outro para retornar a coleção como aStream
.Esse é o melhor de ambos mundos. O cliente pode escolher se deseja a lista ou o fluxo e não precisa criar objetos extras para fazer uma cópia imutável da lista apenas para obter um fluxo.
Isso também adiciona apenas mais 1 método à sua API, para que você não tenha muitos métodos
fonte