2 Operaciones intermedias¶
Filtrado¶
Otra de las operaciones intermedias que se pueden realizar sobre un stream es el filtrado de sus elementos, es decir, la generación de un nuevo stream que sólo contenga algunos de los elementos del stream original. Java nos proporciona distintos métodos:
-
Stream<T> distinct()
: retorna un nuevo stream con los elementos del stream original, excepto aquellos que estuvieran repetidos. Para determinar que dos elementos son iguales se usará al métodoequals()
del elemento. Ejemplo:Stream<T> limit(long maxSize)
: retorna un nuevo stream con tan sólo maxSize elementos del stream original, atendiendo al orden intrínseco del mismo. Tiene un mal rendimiento en streams paralelos ordenados. Ejemplo:
Stream<T> filter(Predicate<? super T> predicate)
: retorna un nuevo stream que sólo incorpora los elementos del stream original que cumplan el predicado recibido. Ejemplo:
Stream<T> skip(long n)
: retorna un nuevo stream en el que no se incluyen los primeros n elementos del stream original pero sí se incluye el resto. No proporciona un buen rendimiento en streams paralelos ordenados. Ejemplo:
default Stream<T> dropWhile(Predicate <? super T> predicate)
: retorna un nuevo stream con el primer elemento que no cumpla el predicado y el resto de elementos, independientemente de si cumplen el predicado o no. Proporciona un mal rendimiento con streams paralelos ordenados:
default Stream<T> takeWhile(Predicate<? super T> predicate)
: mientras los elementos cumplan el predicado se van incluyendo en el stream, pero en cuanto se encuentra un elemento que no cumple el predicado se deja de incluir el resto de elementos, incluso aunque cumplan el predicado. Ejemplo:
Ordenación¶
Algunos streams son ordenados, es decir, que sus elementos poseen un determinado orden intrínseco significativo, conocido como encounter order. Por ejemplo, un stream cuya fuente de datos corresponda a una lista creará un stream ordenado, cuyo encounter order será el orden en el que los elementos están situados en la lista. Sin embargo, otros streams no son ordenados, en el sentido de que sus elementos no tienen un orden intrínseco significativo. Por ejemplo, un stream cuya fuente de datos sea un conjunto (Set) será un stream sin encounter order, ya que un conjunto los elementos no tienen un orden preestablecido.
El hecho de que un stream sea ordenado o no dependerá del tipo de fuente de datos asociada y de las operaciones intermedias anteriores que hayamos realizado mediante las que se ha obtenido el stream.
Algunas operaciones trabajan por defecto en base a este encounter order, imponiendo una restricción acerca del orden en el que los elementos deben ser procesados, como por ejemplo las operaciones intermedias limit o skip.
Sin embargo, existen otras operaciones que no tienen en cuenta el encounter order, como por ejemplo forEach. Si se ejecuta sobre un stream paralelo, no hay ninguna garantía sobre en que orden se aplica la acción a los elementos. Si queremos que sí se tenga en cuenta el orden, entonces tendríamos que usar el método void forEachOrdered(Consumer<? super T> action)
. Normalmente se usa encadenado después de llamar a un método de ordenación que habrá ordenado el stream. La ventaja de este método es que se garantiza que la acción se aplica a los elementos en el orden intrínseco del stream, incluso aunque éste se trate de un stream paralelo, aunque conlleve un peor rendimiento.
Al trabajar con streams secuenciales, el encounter order no afecta al rendimiento de la aplicación, pero si trabajamos con streams paralelos, el empleo del encounter order por parte de algunos operadores pueden afectar en gran medida al rendimiento general de la aplicación. Dependiendo de la operación de la que se trate, será necesario procesar a la vez más de un elemento del stream a partir del anterior, en el que no se tenga en cuenta el encounter order. Al ejecutar el método unordered()
, tan solo se está creando un nuevo stream en el que se ha borrado el indicador de que el encounter order debe tenerse en cuenta. Normalmente, esta operación de desactivación del encounter order se realiza con el objetivo de mejorar el rendimiento en streams paralelos.
Por otra parte, si queremos obtener un stream ordenado a partir de otro desordenado o a partir de otro stream ordenado pero por un orden distinto, podemos usar el método sorted()
, en cuyo caso los elementos del stream deben implementar la interfaz Comparable
para determinar el orden en el que deben ser ordenados. Otra posibilidad es usar una versión sobrecargada de dicho método que recibe un objeto Comparator
como argumento. Ejemplo:
Ejemplo utilizando Comparator
:
Transformación¶
Java nos proporciona distintos métodos:
-
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
: retorna un nuevo stream obtenido a partir de aplicar la función de transformación indicada a cada uno de los elementos del stream original. El tipo del stream resultante corresponderá al tipo de retorno de la función de transformación, que puede ser distinto al tipo del stream original, pero contendrá tantos elementos como éste. -
Métodos que permiten obtener un stream de un tipo primitivo a partir de uno que no lo sea:
-
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
: retorna unDoubleStream
correspondiente de aplicar a cada elemento del stream original la función a double recibida: -
IntStream mapToInt(ToIntFunction<? super T> mapper)
: retorna unIntStream
correspondiente de aplicar a cada elemento del stream original la función de conversión a int recibida. Ejemplo: -
LongStream mapToLong(ToLongFunction<? super T> mapper)
: retorna unLongStream
correspondiente de aplicar a cada elemento del stream original la función de conversión a long recibida.
-
-
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
: cuando una función de transformación retorna un stream y se aplica esta función con el método map, el stream resultante es unStream<Stream<Tipo>>
. En estos casos, es más óptimo obtener un únicoStream<Tipo>
que contuviera concatenados todos los elementos de todos los substreams. A este proceso se le conoce como aplanado (flat) de substreams.En el ejemplo, muestra los valores
1
(proveniente del primer substream),1
,2
(provenientes del segundo substream) y1
,2
y3
, provenientes del tercer substream, en este orden.Si nos interesa que el tipo del stream resultante fuera primitivo, podemos usar los métodos
flatMapToDouble
,flatMapToInt
oflatMapToLong
.