¿Por qué Stream <T> no implementa Iterable <T>?

262

En Java 8 tenemos la clase Stream <T> , que curiosamente tiene un método

Iterator<T> iterator()

Por lo tanto, es de esperar que implemente la interfaz Iterable <T> , que requiere exactamente este método, pero ese no es el caso.

Cuando quiero iterar sobre un Stream usando un bucle foreach, tengo que hacer algo como

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

¿Me estoy perdiendo de algo?

roim
fuente
77
sin mencionar que los otros 2 métodos de iterable (forEach y spliterator) también están en Stream
njzk2
1
esto es necesario para pasar Streama las API heredadas que esperaIterable
ZhongYu
12
Una buena IDE (por ejemplo, IntelliJ) le pedirá que simplificar el código en el getIterable()quereturn s::iterator;
Zhongyu
24
No necesitas un método en absoluto. Cuando tenga un Stream y desee un Iterable, simplemente pase stream :: iterator (o, si lo prefiere, () -> stream.iterator ()), y listo.
Brian Goetz el
77
Lamentablemente no puedo escribir for (T element : stream::iterator), por lo que aún preferiría que Stream también implementara Iterableo un método toIterable().
Thorsten

Respuestas:

197

La gente ya ha preguntado lo mismo en la lista de correo ☺. La razón principal es que Iterable también tiene una semántica repetible, mientras que Stream no lo es.

Creo que la razón principal es que Iterableimplica la reutilización, mientras que Streames algo que solo se puede usar una vez, más como un Iterator.

Si se Streamextiende Iterable, el código existente podría sorprenderse cuando recibe un Iterablemensaje que se lanza por Exceptionsegunda vez for (element : iterable).

kennytm
fuente
22
Curiosamente, ya había algunos iterables con este comportamiento en Java 7, por ejemplo, DirectoryStream: aunque DirectoryStream se extiende Iterable, no es un Iterable de propósito general, ya que solo admite un único Iterator; Invocar el método de iterador para obtener un segundo iterador o sucesivos arroja IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
roim
32
Desafortunadamente, no hay nada en la documentación Iterablesobre si iteratordebería o no ser invocable varias veces. Eso es algo que deberían poner allí. Esto parece ser más una práctica estándar que una especificación formal.
Lii
77
Si van a usar excusas, pensarías que al menos podrían agregar un método asIterable (), o sobrecargas para todos los métodos que solo toman Iterable.
Trejkaz
26
¿Quizás la mejor solución hubiera sido hacer que el foreach de Java aceptara un <T> Iterable y potencialmente un Stream <T>?
devorado elysium
3
@Lii Según las prácticas estándar, es bastante fuerte.
biziclop
161

Para convertir un Streama un Iterable, puedes hacer

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

Para pasar Streama un método que espera Iterable,

void foo(Iterable<X> iterable)

simplemente

foo(stream::iterator) 

sin embargo, probablemente se ve divertido; podría ser mejor ser un poco más explícito

foo( (Iterable<X>)stream::iterator );
ZhongYu
fuente
67
También puede usar esto en un bucle for(X x : (Iterable<X>)stream::iterator), aunque se ve feo. Realmente, toda la situación es simplemente absurda.
Aleksandr Dubinsky
24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay
11
No entiendo la sintaxis de dos puntos en este contexto. ¿Cuál es la diferencia entre stream::iteratory stream.iterator(), eso hace que el primero sea aceptable para un Iterablepero no el segundo?
Daniel C. Sobral
20
Respondiéndome a mí mismo: Iterablees una interfaz funcional, por lo que pasar una función que la implemente es suficiente.
Daniel C. Sobral
55
Tengo que estar de acuerdo con @AleksandrDubinsky, el verdadero problema es que hay una solución simétrica para (X x: (Iterable <X>) stream :: iterator), que solo permite la misma operación aunque con un gran ruido de código. Pero entonces esto es Java y hemos tolerado List <String> list = new ArrayList (Arrays.asList (array)) durante mucho tiempo :) Aunque los poderes existentes podrían ofrecernos todos List <String> list = array. toArrayList (). Pero lo realmente absurdo es que, al alentar a todos a usar forEach on Stream, las excepciones deben ser necesariamente desmarcadas [despotricada terminada].
El Coordinador
10

Me gustaría señalar que StreamEx implementa Iterable(y Stream), así como una gran cantidad de otras funcionalidades inmensamente increíbles que faltan Stream.

Aleksandr Dubinsky
fuente
8

Puede usar una secuencia en un forbucle de la siguiente manera:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Ejecute este fragmento aquí )

(Esto utiliza una conversión de interfaz funcional Java 8).

(Esto está cubierto en algunos de los comentarios anteriores (por ejemplo, Aleksandr Dubinsky ), pero quería sacarlo en una respuesta para hacerlo más visible).

Rico
fuente
Casi todos necesitan verlo dos o tres veces antes de darse cuenta de cómo se compila. Es absurdo (Esto está cubierto en la respuesta a los comentarios en la misma secuencia de comentarios, pero quería agregar el comentario aquí para hacerlo más visible.)
ShioT
7

kennytm describió por qué no es seguro tratar un Streamcomo un Iterable, y Zhong Yu ofreció una solución alternativa que permite usar un Streamcomo en Iterable, aunque de manera insegura. Es posible obtener lo mejor de ambos mundos: un reutilizable Iterablede uno Streamque cumpla con todas las garantías hechas por la Iterableespecificación.

Nota: SomeTypeno es un parámetro de tipo aquí; debe reemplazarlo con un tipo adecuado (por ejemplo, String) o recurrir a la reflexión

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

Hay una gran desventaja:

Los beneficios de la iteración perezosa se perderán. Si planeaba iterar inmediatamente sobre todos los valores en el hilo actual, cualquier sobrecarga será insignificante. Sin embargo, si planeaba iterar solo parcialmente o en un hilo diferente, esta iteración inmediata y completa podría tener consecuencias no deseadas.

La gran ventaja, por supuesto, es que puede reutilizar Iterable, mientras (Iterable<SomeType>) stream::iteratorque solo permitiría un solo uso. Si el código de recepción iterará en la colección varias veces, esto no solo es necesario, sino que probablemente sea beneficioso para el rendimiento.

Zenexer
fuente
1
¿Has intentado compilar tu código antes de responder? No funciona
Tagir Valeev
1
@TagirValeev Sí, lo hice. Necesita reemplazar T con el tipo apropiado. Copié el ejemplo del código de trabajo.
Zenexer
2
@TagirValeev Lo probé nuevamente en IntelliJ. Parece que IntelliJ a veces se confunde con esta sintaxis; Realmente no he encontrado un patrón para ello. Sin embargo, el código se compila bien e IntelliJ elimina el aviso de error después de la compilación. Supongo que es solo un error.
Zenexer
1
Stream.toArray() devuelve una matriz, no un Iterable , por lo que este código aún no se compila. pero puede ser un error en eclipse, ya que IntelliJ parece compilarlo
benez
1
@Zenexer ¿Cómo pudo asignar una matriz a un Iterable?
radiantRazor
3

Streamno implementa Iterable. La comprensión general de Iterablees todo lo que se puede repetir, a menudo una y otra vez. Streampuede no ser rejugable

La única solución que se me ocurre, en la que también se puede reproducir un iterable basado en una secuencia, es volver a crear la secuencia. Estoy usando un Suppliersiguiente para crear una nueva instancia de transmisión, cada vez que se crea un nuevo iterador.

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }
Ashish Tyagi
fuente
2

Si no le importa usar bibliotecas de terceros, cyclops-react define un Stream que implementa Stream e Iterable y que también se puede volver a reproducir (resolviendo el problema descrito por kennytm ).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

o: -

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Divulgación Soy el desarrollador principal de cyclops-react]

John McClean
fuente
0

No es perfecto, pero funcionará:

iterable = stream.collect(Collectors.toList());

No es perfecto, ya que se ha podido recuperar todos los elementos de la corriente y ponerlos en que List, lo que no es exactamente lo que Iterabley Streamestán a punto. Se supone que son flojos .

yegor256
fuente
-1

Puede iterar sobre todos los archivos en una carpeta usando Stream<Path>así:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}
BullyWiiPlaza
fuente