Como posso transformar um fluxo em um iterável? [duplicado]

8

Streamherda um método iterator () para produzir um Iterator.

Mas eu preciso de um Iterablee não de um Iterator.

Por exemplo, dada esta sequência:

String input = "this\n" +
        "that\n" +
        "the_other";

... Eu preciso passar essas partes da string como uma Iterablepara uma biblioteca específica. Chamar input.lines()produz a Stream. Então, eu estaria pronto se pudesse transformar isso Streamem um Iterablede seus elementos.

Basil Bourque
fonte
@ Naman Related, mas isso parece ser o inverso do que Basil está fazendo nesta sessão de perguntas e respostas.
Slaw 23/01
1
Outra maneira de obter um Iterable seria coletar em uma Lista.
matt

Respostas:

12

Conforme explicado em Por que o Stream <T> não implementa Iterable <T>? , Iterabletem a expectativa de poder fornecer Iteratormais de uma vez, o que Streamnão pode atender. Portanto, embora você possa criar um Iterableout de um Streampara um uso ad-hoc, você deve ter cuidado para saber se existem tentativas de iterá-lo várias vezes.

Como você disse: " Eu preciso passar essas partes da string como uma Iterablepara uma biblioteca específica ", não há solução geral, pois o código que está usando Iterableestá fora do seu controle.

Mas se você é quem criou o fluxo, é possível criar um válido Iterableque simplesmente repita a construção do fluxo sempre que Iteratorfor solicitado:

Iterable<String> lines = () -> "this\nthat\nthe_other".lines().iterator();

Isso atende à expectativa de oferecer suporte a um número arbitrário de iterações, sem consumir mais recursos do que um único fluxo ao ser percorrido apenas uma vez.

for(var s: lines) System.out.println(s);
lines.forEach(System.out::println);
System.out.println(String.join("\n", lines));
Holger
fonte
Excelente explicação. Vale a pena acrescentar (como um adendo um pouco mais avançado) que, embora seja possível dizer que Iterable<String> lines = "this\nthat\nthe_other".lines()::iterator;esse é um daqueles casos raros em que uma referência de método não é a mesma que uma lambda e, de fato, teria efeito indesejável.
Klitos Kyriacou 23/01
3
As referências do método @KlitosKyriacou do formulário expression::namenunca são as mesmas que as expressões lambda, nem mesmo para System.out::println. Mas aqui, temos um dos casos em que isso importa
Holger
Nesse caso, as linhas seriam chamadas sempre, mas não vejo a diferença entre um método e um lambda se Stream<String> lines = str.lines();você criar o Iterable<String> iter = lines::iteratorversus ()->lines.iterator().
matt
2
@matt ninguém disse que havia uma diferença entre stream::iteratore () -> stream.iterator(). De fato, minha resposta disse com precisão, não há solução geral para converter um fluxo existente em um iterável válido que permita várias iterações. Repetir a construção do fluxo toda vez que um iterador é solicitado, ou seja, aqui significa chamar lines()sempre, é o que faz a diferença.
Holger
3
@matt bem, formalmente, não é o mesmo. expression::namesignifica " avaliar expressionuma vez e capturar o resultado ", enquanto que () -> expression.name(…)significa " avaliarexpression cada vez que o corpo da função é avaliado ". As diferenças são pequenas quando " expression" é apenas uma variável local, mas mesmo assim é diferente, ou seja, stream::iteratorse comportará de maneira diferente de () -> stream.iterator()quando streamé null. Portanto, minha declaração ainda é válida, expression::namesempre é diferente de uma expressão lambda, a questão é: quanto isso importa para o meu caso de uso específico.
Holger
3

tl; dr

Basta lançar , não há necessidade de converter.

Transmitir Stream < String >para Iterable < String >.

Detalhes

CUIDADO Consulte Resposta de Holger, explicando os perigos do uso de um fluxo de backup Iterable.

Sim, você pode fazer um Iterablede a Stream.

A solução é simples, mas não óbvia. Veja este post no FAQ Lambda de Maurice Naftalin .

o iterator() método em BaseStream(superclasse de Stream) retornando Iteratora coincide com o mesmo nome do iterator()método retornando a Iteratorconforme exigido pela Iterableinterface. As assinaturas do método correspondem. Então, na verdade, podemos converter Streampara a Iterable, sem necessidade de conversão.

Faça sua entrada.

String input = "this\n" +
        "that\n" +
        "the_other";
Stream < String > stream = input.lines() ;

Lance isso Stream<String>para Iterable<String>.

Iterable< String > iterable = ( Iterable < String > ) stream ;  // Cast `Stream < String >` to `Iterable < String >`. 

Teste os resultados.

for ( String s : iterable ) 
{
    System.out.println( "s = " + s );
}

Veja este código executado ao vivo em IdeOne.com .

s = isso

s = isso

s = o_outro

CAVEAT Cuidado com o risco de backup em fluxo Iterable. Explicado no correto resposta de Holger .

Basil Bourque
fonte
7
Tecnicamente, você não está transmitindo Stream<String>porque stream::iteratornão é do tipo Stream<String>.
kaya3 22/01
3
Não tenho certeza se o elenco é a terminologia correta. Você está criando uma instância Iterableatravés de uma referência de método; o (Iterable<String>)just diz ao compilador qual interface funcional é o destino.
Slaw 23/01
@Slaw & @ kaya3 Essas sutilezas escaparam ao meu entendimento. Não vejo (Iterable<String>)nada além de um elenco, mas certamente não entendo a situação, pois seus comentários me levaram a colocar um conjunto extra de parênteses em torno do ( (Iterable<String>) stream )qual causa falha no código - provando que meu entendimento é falho. Portanto, edite minha resposta para esclarecer ou publique sua própria resposta com uma explicação melhor.
Basil Bourque
4
@Law o problema é, é correto que haja um elenco, mas não é correto dizer que o elenco é a solução. A solução consiste em uma referência de método e uma conversão. A conversão pode ser substituída por outra construção que forneça o tipo de destino pretendido, por exemplo, passando a referência do método para um método que espera um iterável ou atribuindo-o a uma variável do tipo iterável. Mas a solução ainda requer uma referência de método ou expressão lambda para a conversão. Portanto, não faz sentido dizer " apenas transmitir, em vez de converter " quando ainda há uma conversão (código do adaptador) e uma transmissão.
Holger
2
Iterable é efetivamente uma interface funcional, você está criando um Iterable com o método iterador substituído pelo stream.iterator. Seria efetivamente (Iterable<String>)()->stream.iterator()ou ainda mais explicitamente new Iterable<String>(){ public Iterator<String> iterator(){ return stream.iterator();}. Portanto, você não está transmitindo um fluxo para o Iterable, o que falharia.
matt