streams procesamiento parte operaciones libreria funcionales funcional examples ejemplos datos con collection java java-8

java - procesamiento - ¿Por qué Iterable<T> no proporciona los métodos stream() y parallelStream()?



stream java ejemplos (3)

Esto no fue una omisión; hubo una discusión detallada sobre la lista de EG en junio de 2013.

La discusión definitiva del Grupo de Expertos está arraigada en este hilo .

Si bien parecía "obvio" (incluso para el Grupo de Expertos, inicialmente) que stream() parecía tener sentido en Iterable , el hecho de que Iterable fuera tan general se convirtió en un problema, porque la firma obvia:

Stream<T> stream()

No era siempre lo que ibas a querer. Algunas cosas que eran Iterable<Integer> preferirían que su método de transmisión retornara un IntStream , por ejemplo. Pero poner el método stream() en la jerarquía lo haría imposible. Así que, en lugar de eso, hicimos que sea realmente fácil hacer un Stream partir de un Iterable , al proporcionar un método spliterator() . La implementación de stream() en Collection es solo:

default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }

Cualquier cliente puede obtener la transmisión que desea de un Iterable con:

Stream s = StreamSupport.stream(iter.spliterator(), false);

Al final, llegamos a la conclusión de que agregar stream() a Iterable sería un error.

Me pregunto por qué la interfaz de Iterable no proporciona los métodos stream() y parallelStream() . Considere la siguiente clase:

public class Hand implements Iterable<Card> { private final List<Card> list = new ArrayList<>(); private final int capacity; //... @Override public Iterator<Card> iterator() { return list.iterator(); } }

Es una implementación de una Mano, ya que puedes tener cartas en tu mano mientras juegas un Juego de Cartas Coleccionables.

Esencialmente, envuelve una List<Card> , asegura una capacidad máxima y ofrece algunas otras características útiles. Es mejor implementarlo directamente como una List<Card> .

Ahora, por conveniencia, pensé que sería bueno implementar Iterable<Card> , de modo que pueda usar bucles for for si lo desea. (Mi clase de Hand también proporciona un método de get(int index) , por lo que la Iterable<Card> está justificada en mi opinión.)

La interfaz de Iterable proporciona lo siguiente (dejado fuera javadoc):

public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }

Ahora puedes obtener un flujo con:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

Así que en la pregunta real:

  • ¿Por qué Iterable<T> no proporciona métodos predeterminados que implementen stream() y parallelStream() , no veo nada que haga esto imposible o no deseado?

Sin embargo, una pregunta relacionada que encontré es la siguiente: ¿Por qué Stream <T> no implementa Iterable <T>?
Lo que, por extraño que parezca, es sugerir que lo haga al revés.


Hice una investigación en varias de las listas de correo de lambda del proyecto y creo que encontré algunas discusiones interesantes.

No he encontrado una explicación satisfactoria hasta ahora. Después de leer todo esto, llegué a la conclusión de que era solo una omisión. Pero se puede ver aquí que se discutió varias veces a lo largo de los años durante el diseño de la API.

Expertos en especificaciones de Lambda Libs

Encontré una discusión sobre esto en la lista de correo de Expertos en Especificaciones de Lambda Libs :

Bajo Iterable/Iterator.stream() Sam Pullara dijo:

Estaba trabajando con Brian para ver cómo se podría implementar la funcionalidad de límite / transmisión secundaria [1] y sugirió que la conversión a Iterator era la manera correcta de hacerlo. Había pensado en esa solución, pero no encontré ninguna forma obvia de tomar un iterador y convertirlo en una secuencia. Resulta que está ahí, solo necesitas convertir el iterador en un separador y luego convertirlo en un flujo. Así que esto me lleva a revisar si deberíamos tener estos colgando uno de Iterable / Iterator directamente o ambos.

Mi sugerencia es al menos tenerlo en Iterator para que pueda moverse limpiamente entre los dos mundos y también sería fácilmente detectable en lugar de tener que hacerlo:

Streams.stream (Spliterators.spliteratorUnknownSize (iterador, Spliterator.ORDERED))

Y luego Brian Goetz respondió :

Creo que el punto de Sam fue que hay muchas clases de bibliotecas que te dan un iterador, pero no te dejan escribir necesariamente tu propio separador. Así que todo lo que puedes hacer es llamar a stream (spliteratorUnknownSize (iterator)). Sam sugiere que definamos Iterator.stream () para que lo haga por usted.

Me gustaría mantener los métodos stream () y spliterator () como para los escritores de bibliotecas / usuarios avanzados.

Y después

"Dado que escribir un Spliterator es más fácil que escribir un Iterator, preferiría simplemente escribir un Spliterator en lugar de un Iterator (Iterator es tan 90s :)"

Sin embargo, te estás perdiendo el punto. Hay miles de clases por ahí que ya te dan un iterador. Y muchos de ellos no están listos para la separación.

Discusiones previas en la lista de correo de Lambda

Es posible que esta no sea la respuesta que está buscando, pero en la lista de correo del Proyecto Lambda esto se discutió brevemente. Quizás esto ayude a fomentar una discusión más amplia sobre el tema.

En las palabras de Brian Goetz bajo Streams from Iterable :

Dar un paso atrás...

Hay muchas formas de crear un Stream. Cuanta más información tenga sobre cómo describir los elementos, mayor será la funcionalidad y el rendimiento que la biblioteca de flujos puede brindarle. En orden de menor a mayor información, son:

Iterador

Iterador + tamaño

Separador

Spliterator que conoce su tamaño.

Spliterator que conoce su tamaño y además sabe que todas las subdivisiones conocen su tamaño.

(Algunos pueden sorprenderse al descubrir que podemos extraer el paralelismo incluso de un iterador estúpido en los casos en que Q (trabajo por elemento) no es trivial).

Si Iterable tuviera un método stream (), simplemente envolvería un iterador con un Spliterator, sin información de tamaño. Pero, la mayoría de las cosas que son repetibles tienen información sobre el tamaño. Lo que significa que estamos sirviendo corrientes deficientes. Eso no es tan bueno.

Una desventaja de la práctica de API descrita por Stephen aquí, de aceptar Iterable en lugar de Collection, es que está forzando las cosas a través de un "pequeño conducto" y, por lo tanto, descartando información de tamaño cuando podría ser útil. Eso está bien si todo lo que estás haciendo para hacer es para cada uno, pero si quieres hacer más, es mejor si puedes conservar toda la información que deseas.

El valor predeterminado provisto por Iterable sería realmente malo: descartaría el tamaño aunque la gran mayoría de los Iterables conozcan esa información.

¿Contradicción?

Aunque parece que la discusión se basa en los cambios que realizó el Grupo de expertos en el diseño inicial de Streams que inicialmente se basó en iteradores.

Aun así, es interesante observar que en una interfaz como Colección, el método de flujo se define como:

default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }

Cuál podría ser exactamente el mismo código que se utiliza en la interfaz de Iterable.

Entonces, esta es la razón por la que dije que esta respuesta probablemente no es satisfactoria, pero sigue siendo interesante para la discusión.

Evidencia de Refactorización

Continuando con el análisis en la lista de correo, parece que el método splitIterator estaba originalmente en la interfaz de la Colección, y en algún momento en 2013 lo cambiaron a Iterable.

Tire de splitIterator hacia arriba desde Colección a Iterable .

Conclusión / ¿Teorías?

Entonces, lo más probable es que la falta del método en Iterable sea solo una omisión, ya que parece que deberían haber movido el método de transmisión también cuando movieron el SplitIterator de Collection a Iterable.

Si hay otras razones no son evidentes. ¿Alguien más tiene otras teorías?


Si conoce el tamaño, puede usar java.util.Collection que proporciona el método stream() :

public class Hand extends AbstractCollection<Card> { private final List<Card> list = new ArrayList<>(); private final int capacity; //... @Override public Iterator<Card> iterator() { return list.iterator(); } @Override public int size() { return list.size(); } }

Y entonces:

new Hand().stream().map(...)

Enfrenté el mismo problema y me sorprendió que mi implementación de Iterable pudiera extenderse muy fácilmente a una implementación de AbstractCollection simplemente agregando el método size() (por suerte tenía el tamaño de la colección :-)

También debe considerar anular Spliterator<E> spliterator() .