¿Cómo puedo convertir un Stream en un Iterable? [duplicar]

8

Streamhereda un método iterator () para producir un Iterator.

Pero necesito una en Iterablelugar de una Iterator.

Por ejemplo, dada esta cadena:

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

... Necesito pasar esas partes de cadena como una Iterablea una biblioteca específica. Llamar input.lines()rinde a Stream. Así que estaría listo si pudiera convertir eso Streamen uno Iterablede sus elementos.

Albahaca Bourque
fuente
@Naman Related, pero parece ser lo contrario de lo que Basil está haciendo en estas preguntas y respuestas.
Slaw
1
Otra forma de obtener un Iterable sería simplemente recolectar en una Lista.
mate

Respuestas:

12

Como se explica en ¿Por qué Stream <T> no implementa Iterable <T>? , Iterabletiene la expectativa de poder proporcionar Iteratormás de una vez, lo que Streamno puede cumplir. Entonces, si bien puede crear un Iterablefuera de a Streampara un uso ad-hoc, debe tener cuidado con la posibilidad de que existan intentos de iterarlo varias veces.

Como usted dijo: " Necesito pasar esas partes de la cadena como una Iterablea una biblioteca específica ", no hay una solución general ya que el código que usa Iterableestá fuera de su control.

Pero si usted es quien crea la secuencia, es posible crear una válida Iterableque simplemente repita la construcción de la secuencia cada vez que Iteratorse solicita:

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

Esto cumple la expectativa de soportar un número arbitrario de iteraciones, sin consumir más recursos que una sola secuencia cuando se atraviesa solo una vez.

for(var s: lines) System.out.println(s);
lines.forEach(System.out::println);
System.out.println(String.join("\n", lines));
Holger
fuente
Excelente explicación Podría valer la pena agregar (como un apéndice un poco más avanzado) que si bien es posible decir que Iterable<String> lines = "this\nthat\nthe_other".lines()::iterator;este es uno de esos casos raros en los que una referencia de método no es lo mismo que una lambda y, de hecho, tendría un efecto no deseado.
Klitos Kyriacou
3
Las referencias al método @KlitosKyriacou del formulario expression::namenunca son las mismas que las expresiones lambda, ni siquiera para System.out::println. Pero aquí, tenemos uno de los casos donde importa
Holger,
En este caso, las líneas se llamarían cada vez, pero no veo la diferencia entre un método y una lambda si Stream<String> lines = str.lines();creas el Iterable<String> iter = lines::iteratorversus ()->lines.iterator().
mate
2
@matt nadie dijo que hubiera una diferencia entre stream::iteratory () -> stream.iterator(). De hecho, mi respuesta dijo con precisión que no existe una solución general para convertir una secuencia existente en una iteración válida que permita múltiples iteraciones. Repetir la construcción de la secuencia cada vez que se solicita un iterador, es decir, aquí significa llamar lines()cada vez, es lo que marca la diferencia.
Holger
3
@matt bueno, formalmente, no es lo mismo. expression::namesignifica " evaluar expressionuna vez y capturar el resultado ", mientras que () -> expression.name(…)significa " evaluar expressioncada vez que se evalúa el cuerpo de la función ". Las diferencias son pequeñas cuando " expression" es solo una variable local, pero aun así, es diferente, es decir, stream::iteratorse comportará de manera diferente a () -> stream.iterator()cuando streames null. Por lo tanto, mi afirmación aún se mantiene, expression::namesiempre es diferente a una expresión lambda, la pregunta es, ¿cuánto importa para mi caso de uso específico?
Holger
3

tl; dr

Recién lanzado , no hay necesidad de convertir.

Cast Stream < String >a Iterable < String >.

Detalles

PRECAUCIÓN Consulte la Respuesta de Holger que explica los peligros del uso de un flujo respaldado Iterable.

Sí, puedes hacer un Iterablede a Stream.

La solución es simple, pero no obvia. Vea esta publicación en las Preguntas frecuentes sobre Lambda de Maurice Naftalin .

El iterator()método en BaseStream(superclase de Stream) que devuelve un Iteratorcoincide con el mismo nombre del iterator()método que devuelve un Iteratorcomo lo requiere la Iterableinterfaz. Las firmas del método coinciden. Por lo tanto, podemos convertirlo en Streama Iterable, no se necesita conversión.

Haz tu aportación.

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

Echa eso Stream<String>a Iterable<String>.

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

Prueba los resultados.

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

Vea este código en vivo en IdeOne.com .

s = esto

s = eso

s = el_otro

CUIDADO Tenga cuidado con el riesgo de la transmisión de respaldo Iterable. Explicado en la respuesta correcta de Holger .

Albahaca Bourque
fuente
77
Técnicamente, no estás lanzando Stream<String>porque stream::iteratorno es de tipo Stream<String>.
kaya3
3
No estoy seguro de que el casting sea la terminología correcta. Estás creando una instancia a Iterabletravés de una referencia de método; el (Iterable<String>)simplemente le dice al compilador qué interfaz funcional es el objetivo.
Slaw
@Slaw & @ kaya3 Estas sutilezas han escapado a mi comprensión. No veo cómo (Iterable<String>)es nada más que un elenco, sin embargo, ciertamente no entiendo la situación, ya que sus comentarios me impulsaron a colocar un conjunto adicional de parens ( (Iterable<String>) stream )que causa que el código falle, lo que demuestra que mi comprensión es defectuosa. Así que edite mi respuesta para aclarar o publique su propia respuesta con una mejor explicación.
Basil Bourque
44
@Slaw el problema es que es correcto que haya un yeso, pero no es correcto decir que el yeso es la solución. La solución consiste en un método de referencia y un molde. La conversión podría reemplazarse por otra construcción que proporcione el tipo de destino deseado, por ejemplo, pasar la referencia del método a un método que espera un iterable o asignarlo a una variable de tipo iterable. Pero la solución aún requiere una referencia de método o una expresión lambda para la conversión. Por lo tanto, no tiene sentido decir " solo emitir, en lugar de convertir " cuando todavía hay una conversión (código de adaptador) y un elenco.
Holger
2
Iterable es efectivamente una interfaz funcional, está creando un Iterable con el método iterador reemplazado por stream.iterator. Sería efectivamente (Iterable<String>)()->stream.iterator()o incluso más explícitamente new Iterable<String>(){ public Iterator<String> iterator(){ return stream.iterator();}. Por lo tanto, no está emitiendo un Stream a Iterable, lo que podría fallar.
mate