0% encontró este documento útil (0 votos)
60 vistas66 páginas

Java Streams

El documento compara la programación imperativa y reactiva, destacando que la primera se enfoca en cómo realizar tareas paso a paso, mientras que la segunda se basa en la reacción a cambios en flujos de datos. También se resumen las mejoras clave introducidas en Java desde la versión 8 hasta la 22, incluyendo expresiones lambda, Streams API, y nuevas características de concurrencia y rendimiento. Se enfatiza la evolución de Java para adaptarse a las necesidades del desarrollo de software moderno.

Cargado por

Luis Cornejo
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
60 vistas66 páginas

Java Streams

El documento compara la programación imperativa y reactiva, destacando que la primera se enfoca en cómo realizar tareas paso a paso, mientras que la segunda se basa en la reacción a cambios en flujos de datos. También se resumen las mejoras clave introducidas en Java desde la versión 8 hasta la 22, incluyendo expresiones lambda, Streams API, y nuevas características de concurrencia y rendimiento. Se enfatiza la evolución de Java para adaptarse a las necesidades del desarrollo de software moderno.

Cargado por

Luis Cornejo
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Claro, aquí va una explicación clara y comparativa entre programación imperativa y

programación reactiva, con ejemplos sencillos para entender sus diferencias:

Programación Imperativa

Es el estilo de programación tradicional, donde le dices al computador cómo hacer algo, paso
a paso.

Características:

• Control explícito del flujo (con if, for, while, etc.).


• El estado se maneja de forma directa.
• Se enfoca en cómo lograr el resultado.

Ejemplo (JavaScript):

javascript
CopiarEditar
let numbers = [1, 2, 3, 4, 5];
let doubled = [];

for (let i = 0; i < [Link]; i++) {


[Link](numbers[i] * 2);
}

[Link](doubled); // [2, 4, 6, 8, 10]

Programación Reactiva

Se basa en flujos de datos y propagación de cambios. En lugar de decir cómo hacer algo paso
a paso, defines reacciones a cambios.

Características:

• Uso de observables, streams y eventos.


• Reacciona a los cambios de datos o eventos.
• Se enfoca en qué hacer cuando algo cambie.

Ejemplo (JavaScript con RxJS):

javascript
CopiarEditar
import { from } from 'rxjs';
import { map } from 'rxjs/operators';

const numbers = from([1, 2, 3, 4, 5]);


[Link](
map(n => n * 2)
).subscribe(result => [Link](result));
// Imprime 2, 4, 6, 8, 10 (uno a uno conforme se procesa)

Diferencias Clave

Característica Imperativa Reactiva


Enfoque Cómo hacerlo Qué hacer cuando cambia
Flujo de
Paso a paso Basado en eventos o flujos de datos
ejecución
Control de estado Manual Automático con propagación de cambios
onClick, setState Reacción a cambios en datos (como en React con
Ejemplo en UI
manual useEffect)

Si quieres, puedo explicarlo con un ejemplo en una interfaz React para ver cómo encaja la
programación reactiva en el desarrollo web moderno. ¿Te gustaría eso?

4o

Tú dijiste:

cambios o mejoras desde la versión Java 8 en adelante


ChatGPT Plus

¡Buena pregunta! Desde Java 8 en adelante, el lenguaje ha recibido muchas mejoras


importantes, tanto en la sintaxis como en la API del JDK y el rendimiento. Aquí tienes un
resumen con las versiones clave y sus principales mejoras:

Java 8 (marzo 2014)

La versión más revolucionaria en años

• Expresiones lambda
Permiten pasar funciones como parámetros:

java
CopiarEditar
[Link](x -> [Link](x));

• Streams API
Programación funcional para colecciones.

java
CopiarEditar
[Link]().filter(x -> x > 5).collect([Link]());
• Default Methods en interfaces
Métodos con implementación dentro de interfaces.
• Optional
Para evitar NullPointerException.
• Nueva API de fecha y hora ([Link])
Moderna y segura frente a errores.

Java 9 (2017)

• Modular System (Project Jigsaw)


División del JDK en módulos ([Link]).
• REPL con JShell
Ejecuta código Java desde consola interactiva.
• Mejoras en Streams: takeWhile(), dropWhile(), etc.

Java 10 (2018)

• var para inferencia de tipo local:

java
CopiarEditar
var name = "ChatGPT"; // infiere String

Java 11 (2018) LTS (Soporte a largo plazo)

• Nuevos métodos en String, isBlank(), lines(), etc.


• Soporte nativo para HTTP/2 con HttpClient.
• Eliminación del Java EE y CORBA del JDK.
• Puede compilar y ejecutar en un solo comando:

bash
CopiarEditar
java [Link]

Java 12–14 (2019-2020)

• Switch Expressions (preview en 12, estable en 14):

java
CopiarEditar
int result = switch (day) {
case MONDAY -> 1;
case TUESDAY -> 2;
default -> 0;
};
• Text Blocks (desde Java 13, estable en 15): Permite strings multilínea:

java
CopiarEditar
String json = """
{
"name": "Java"
}
""";

Java 15–17 (2020–2021)

Java 17 es LTS

• Records (clases inmutables con menos código):

java
CopiarEditar
record Person(String name, int age) {}

• Pattern Matching para instanceof:

java
CopiarEditar
if (obj instanceof String s) {
[Link]([Link]());
}

• Mejoras de rendimiento y nuevos GC (Garbage Collectors) como ZGC y Shenandoah.

Java 18–21 (2022–2023)

Java 21 también es LTS

• Pattern Matching para switch (más expresivo y seguro).


• Continuaciones y fibras (Project Loom, preview).
• Nuevas APIs de concurrencia estructurada.
• Mejoras en rendimiento del runtime y GC.

En resumen:

Versión Mejora Destacada


8 Lambdas, Streams, Optional
9 Módulos, JShell
10 var
Versión Mejora Destacada
11 HTTP Client, nuevas APIs
14–15 Switch expressions, Text blocks
16–17 Records, Pattern Matching
18–21 Mejoras en concurrencia y rendimiento

Java es uno de los lenguajes de programación más potentes. El lenguaje se volvió


más fuerte cuando Oracle lanzó una nueva versión de Java, Java SE 8, el 18 de
marzo de 2014 con novedades importantes.

1. Expresión Lambda

La expresión Lambda es una función anónima (una función sin nombre) que ayuda
a escribir código en un cierto estilo funcional. Dado que los datos se pueden iterar,
filtrar y extraer, es muy útil, especialmente en la biblioteca de recopilación. La
expresión Lambda también ayuda a reducir la complejidad del código.

Sintaxis

(Parametros) -> Expresión


Código:

(a,b) -> a + b // Esto nos devuelve la suma de a + b

2. Stream API

Stream API ofrece una técnica para el procesamiento de datos de diferentes


maneras, como filtrado, extracción, transformación, con la ayuda del
paquete [Link]. Una de las principales ventajas de Stream API es
que no altera su fuente, es decir, si se filtra un conjunto de datos, se crea un nuevo
conjunto de datos con los datos filtrados en lugar de modificar la fuente
original. Stream API evalúa el código sólo cuando es necesario y no repite el código
más de una vez.

3. Método ForEach ()

Java SE8 ofrece un nuevo método llamado forEach que se define en la


interfaz Iterable. El bucle ForEach () se puede usar en una clase de colección
que extiende la interfaz Iterable. Como este método ingresa un solo parámetro, una
expresión lambda también se puede pasar como parámetro.
Código: Recurre cada edad y la imprime

[Link]( edad -> ([Link](edad);));

4. Método predeterminado
Normalmente, los métodos no abstractos no se pueden agregar a las interfaces.
Pero en el caso de Java 8, podemos agregar esos métodos en las interfaces. Estos
métodos se escriben con una palabra clave predeterminada y se conocen como
métodos predeterminados. Como son métodos no abstractos, también se puede
incluir el cuerpo del método por ej:
Código

public interface Movible(

default void Sonido()(

[Link]("Hola Ana, ¿cómo está mi sonido?");

))

5. Fecha Hora API

En Java 8, se ha introducido una nueva API de tiempo y API de fecha donde las
fechas de manejo están en un método diferente en comparación con otras versiones
de Java. Algunas de las siguientes son clases de hora y fecha que están disponibles
en Java. paquete de tiempo:

• Clase [Link]
• Clase LocalTime
• Clase LocalDateTime
• Clase de MonthDay

Código:

Clock cl = [Link]();

[Link]([Link]());

7. Referencias de métodos

Las referencias de métodos son otra característica que se introduce en Java 8, que
se puede usar en métodos de interfaz funcional. De otra manera, se puede decir
que son un subconjunto de otra característica de Java, las expresiones lambda.
Esto se debe a que también se puede usar una referencia de método si se puede
usar una expresión lambda.
Los métodos pueden ser:

• Referencia al constructor
• Referencia al método estático
• Referencia a un método de instancia
Diferencias entre java 8 y Java 9

Java SE8 es la nueva versión de Java desarrollada por Oracle que


ofrece varias características nuevas. La expresión lambda se
considera como la característica más significativa entre ellas.

Ahora es momento de hablar sobre Java 9, el principal cambio que


tuvo el JDK 9 fue la introducción de un componente llamada "el
módulo" el proyecto Jigsaw.

Es un proyecto que nos ayuda a reducir el tamaño y complejidad


tanto de las aplicaciones java como del propio runtime de java JRE,
de este modo podemos utilizar solamente lo que necesitamos en
nuestra aplicación y no la plataforma java completa.

También tuvimos la implementación del Método Factory que


proviene del patrón de proyecto llamado Factory. La idea es que,
pensando en las relaciones entre objetos, los métodos de interfaz
sean creados sin especificar sus clases concretas. Hablar así puede
parecer extraño pero esta implementación, cuando pensamos en el
método .of() se usa frecuentemente al pensar en Enums en Java.

Al finalizar Java 9, no podemos dejar de mencionar la incorporación


del flujo reactivo por medio del Flow Api, una clase llamada Flow en
el paquete Util de Java que busca mejorar el flujo de procesamiento
asincrónico de datos. Tenemos cuatro clases que trabajan este flujo:
Suscripción, Procesador, Suscriptor y Publicador.

o Clase Suscriptor trabaja como origen de los mensajes y


demandas.
o Clase Publicador trabaja con el procesamiento de las
demandas de forma asincrónica.
o Clase Suscripción se encarga de la conexión entre las clases
Suscriptor y Publicador al manejar los mensajes de control.
o Clase Procesador puede trabajar tanto como la clase
Suscriptor como la clase Publicador.

Una vez mencionadas las características más destacables de esta versión,


pasemos a java 10.
Características de Java 10

1. Inferencia de tipo variable local


Esta nueva característica busca simplificar y mejorar la experiencia del
desarrollador. La nueva sintaxis reducirá la longitud del código relacionado con
Java, al mismo tiempo que mantendrá el compromiso de la seguridad del tipo
estático.

La inferencia de tipo variable local introducirá la palabra clave var, es decir, puede
definir libremente las variables sin tener que especificar el tipo de variable, como por
ej:
Pasamos de esto:

list <String> list = new ArrayList <String> ();

Stream <String> stream = getStream();

A esto:

var list = new ArrayList <String> ();

var stream = getStream();

2- Interfaz del Recolector de Basura

En la estructura anterior de JDK, los componentes que formaban una


implementación del Recolector de Basura (GC) estaban dispersos en varias partes
del código base. Esto ha cambiado en Java 10. Ahora, es una interfaz limpia dentro
del código fuente de JVM para permitir que los colectores alternativos se integren
rápida y fácilmente. Mejorará el aislamiento del código fuente de los diferentes
recolectores de basura.
Características de Java 11

1- Cliente HTTP

La meta con la nueva característica API HttpClient es proporcionar una manera


estándar de consumir ApiRest, usando patrones modernos y mejores prácticas ya
aplicadas en las librerías de terceros mencionadas anteriormente.
Tiene también, soporte para conexiones seguras, HTTP2, websockets y
request asíncronos. Hay 3 conceptos básicos en este API:

• HttpClient:Se encarga de crear la conexión y su configuración.


Proxies, Authenticators, executors pools, cookie manager, etc.
• HttpRequest: Es básicamente el request a un URI. Podemos indicar el
timeout, headers, si el request es asíncrono o síncrono.
• HttpResponse: Objeto para manejar la respuesta del request. En él se
puede ver el status code, body response, headers y más.
2- Ejecución desde archivo de código fuente único
Para ejecutar un programa Java es necesario primero compilarlo a bytecode para
posteriormente ejecutarlo. Se necesitan dos pasos para facilitar la ejecución de los
programas que se componen de un único archivo de código fuente, se suma la
posibilidad de lanzar un programa desde el código fuente. Esto es útil para
programas pequeños o para los casos de estar aprendiendo el lenguaje.
Y por último vamos a repasar algunas características de Java 17 en este artículo.
Características de Java 17

• Se definió un estándar para el switch ya muy usado en Java y otros


lenguajes con un foco en una mejor legibilidad y modelado del
código. Se evita así el uso de repetidos IF/ELSE y el
operador instanceOf. Este estilo es algo más declarativo, podemos
visualizarlo mejor con un ejemplo:
• static String formateo (Object o){

• String formato = "desconocido";

• if(o instanceof Integer i){

• formato = [Link]("int %d", i);

• }else if (o instanceof Long l){

• formato = [Link]("long %d", l);

• }else if(o instanceof Double d){

• formato = [Link]("double %f" , d);

• }else if(o instanceof String s){

• if([Link] > 3){

• formato = [Link]("Cadena Corta %s" + s);

• }else{

• formato = [Link]("Cadena Larga %s" + s);


• }

• }

• return formato;

Ahora podemos hacerlo de esta otra forma:

static String formatoSwitch (Object o){

return switch (o) {

case Integer i -> [Link]("int %d" , i);

case Long l -> [Link]("long %d", l);

case Double d -> [Link]("double %f" , d);

case String s && ([Link] > 3) ->

[Link]("Cadena Corta %s", s);

case String s && ([Link] > 10) ->

[Link]("Cadena Larga %s", s);

default -> [Link]();

};

o Se elimina Applet API de Java, que generaba un archivo


binario que podía ser utilizado para tener pantallas web. Ya
había sido definido como obsoleto en Java 9 pero solo ahora
en Java 11 fue oficialmente eliminado
o Además, se mejoró la parte referente a generación de
números aleatorios, introduciendo una nueva interfaz
llamada RandomGenerator, una API uniforme que puede generar
números para diferentes tipos numéricos.

Finalmente vamos a hablar un poquito sobre Java 18sin ser una versión LTS.
Características de Java 18
Java 18 fue lanzado a principios de 2022 y cuenta con actualizaciones no
enfocadas directamente en el código Java, sino en la plataforma de desarrollo. Fue
estandarizado el conjunto de caracteres con el UTF-8 y se agregó una etiqueta
@Snippet para agregar código fuente en documentaciones creadas para
códigos/proyectos en el lenguaje. Creando una utilización con pocas líneas de
código para generar un simple servidor web para el programador.
La evolución de Java: las 5 características principales de Java 8 a 22
#Java#características de Java#evolución de Java#java22
La evolución de Java: las 5 características principales de Java 8 a Java 22

Java ha evolucionado continuamente para satisfacer las demandas del desarrollo de


software moderno. Este artículo explora las 5 características principales introducidas
en cada versión de Java, desde Java 8 hasta la última versión, Java 22, y ofrece
explicaciones detalladas, ejemplos útiles y orientación para la transición a las últimas
versiones.

Java 8: El cambio de juego


Java 8, lanzado en marzo de 2014, marcó un cambio significativo en el panorama de la
programación Java con la introducción de funciones de programación funcional y la
nueva API Stream. Estas son las 5 características principales de Java 8:

Expresiones Lambda:
Las expresiones Lambda permiten tratar la funcionalidad como un argumento de
método o crear métodos concisos. Por ejemplo:

List<String> names = [Link]("Mahidhar", "Sophia", "Liam");


[Link](name -> [Link](name));

Esta característica simplifica el código, haciéndolo más legible y fácil de mantener,


especialmente en escenarios que involucran procesamiento de colecciones.

API de Stream:
La API de Stream permite realizar operaciones funcionales en colecciones de objetos.
Por ejemplo:

List<String> names = [Link]("Mahidhar", "Sophia", "Liam");


List<String> sortedNames = [Link]()
.sorted()
.collect([Link]());

Facilita operaciones masivas en colecciones, como filtrado, mapeo y reducción,


mejorando la legibilidad y el rendimiento del código.

Clase opcional
. La Optionalclase es un objeto contenedor que se utiliza para contener objetos no nulos,
lo que ayuda a evitar comprobaciones de valores nulos. Ejemplo:

Optional<String> name = [Link]("Mahidhar");


[Link]([Link]::println);

Esto reduce el riesgo NullPointerExceptiony hace que el código sea más expresivo y
revelador de intenciones.
Métodos predeterminados:
Los métodos predeterminados permiten agregar nuevos métodos a las interfaces sin
afectar las implementaciones existentes. Ejemplo:

interface Greeting {
default void sayHello() {
[Link]("Hello!");
}
}

Esto permite la evolución de la interfaz al proporcionar una manera de agregar nuevos


métodos manteniendo la compatibilidad con versiones anteriores.

API de fecha y hora ([Link]).


La nueva API de fecha y hora ofrece una forma completa y flexible de gestionar la fecha
y la hora. Ejemplo:

LocalDate today = [Link]();


LocalDate birthday = [Link](1990, [Link], 15);

Ofrece un enfoque más sólido, versátil y fácil de usar para el manejo de fecha y hora en
comparación con las antiguas clases [Link] .Calendar

Java 9: Modularidad y más


Java 9, lanzado en septiembre de 2017, introdujo el sistema de módulos y otras mejoras.
Estas son las 5 características principales de Java 9:

Sistema de Módulos de la Plataforma Java (JPMS).


JPMS (Project Jigsaw) introduce un sistema modular para Java que permite una mejor
encapsulación y gestión de dependencias. Ejemplo:

module [Link] {
requires [Link];
}

Esto mejora la estructura de la aplicación, mejora la seguridad y reduce el uso de


memoria al permitir la creación de aplicaciones Java más pequeñas y eficientes.

JShell (REPL)
proporciona una herramienta interactiva de línea de comandos para aprender y
prototipar código Java. Ejemplo:

jshell> int sum(int a, int b) { return a + b; }


jshell> sum(2, 3)

Acelera la experimentación y el aprendizaje al proporcionar retroalimentación


inmediata sobre fragmentos de código.
Métodos de fábrica para colecciones.
Los métodos de fábrica simplifican la creación de colecciones inmutables. Ejemplo:

List<String> names = [Link]("Mahidhar", "Sophia", "Liam");

Esto reduce el código repetitivo y garantiza que las colecciones sean inmutables,
mejorando la seguridad y la claridad del código.

API de Stream mejorada.


Las mejoras en la API de Stream incluyen métodos como takeWhile, dropWhiley iterate.
Ejemplo:

[Link]("a", "b", "c", "", "e")


.takeWhile(s -> ![Link]())
.forEach([Link]::println);

Estos proporcionan formas más versátiles y legibles de manipular transmisiones.

Métodos de interfaz privados:


Permite que los métodos privados en las interfaces compartan código entre los métodos
predeterminados. Ejemplo:

interface MyInterface {
default void defaultMethod() {
privateMethod();
}
private void privateMethod() {
[Link]("Private method");
}
}

Esto promueve la reutilización del código dentro de las interfaces, mejorando la


capacidad de mantenimiento.

Java 10: Inferencia de tipos de variables locales


Java 10, lanzado en marzo de 2018, trajo consigo varias mejoras, siendo la inferencia de
tipos de variables locales la más destacada. Estas son las 5 características principales de
Java 10:

La inferencia de tipo de variable local (var)


var permite al compilador inferir el tipo de variables locales, reduciendo el código
repetitivo. Ejemplo:

var list = [Link]("Mahidhar", "Sophia", "Liam");


for (var name : list) {
[Link](name);
}
Esto mejora la legibilidad del código y reduce la redundancia manteniendo la seguridad
de tipos.

Colecciones no modificables.
Métodos prácticos para crear colecciones no modificables. Ejemplo:

var list = [Link]([Link]("Mahidhar", "Sophia", "Liam"));

Esto simplifica la creación de colecciones inmutables, promoviendo el manejo seguro de


datos.

Mejoras del recolector de basura (GC):


Se introduce el GC G1 como el recolector de basura predeterminado. Esto mejora el
rendimiento de la aplicación y reduce la latencia al optimizar la eficiencia de la
recolección de basura.

El uso compartido de datos de clase de aplicación


mejora el tiempo de inicio y el consumo de memoria al compartir metadatos de clase
comunes entre procesos Java. Esto optimiza el uso de recursos, especialmente en
entornos donde varias aplicaciones Java se ejecutan simultáneamente.

Protocolos de enlace locales de subprocesos:


Permiten operaciones locales de subprocesos sin puntos de seguridad globales, lo que
reduce los tiempos de pausa. Esto mejora el rendimiento en aplicaciones multiproceso
al minimizar la sobrecarga de sincronización.

Java 11: Soporte a largo plazo y más


Java 11, lanzado en septiembre de 2018, es una versión con soporte a largo plazo (LTS)
que incorpora numerosas mejoras. Estas son las 5 características principales de Java 11:

Nuevo cliente HTTP.


Una nueva API de cliente HTTP para gestionar la comunicación HTTP/2 y WebSocket.
Ejemplo:

HttpClient client = [Link]();


HttpRequest request = [Link]()
.uri([Link]("[Link]
.build();
HttpResponse<String> response = [Link](request,
[Link]());
[Link]([Link]());

Esto simplifica la comunicación HTTP, admitiendo estándares web modernos y


operaciones asincrónicas.

Métodos de cadena:
Agrega varios métodos útiles a la Stringclase, como isBlank, linesy strip. Ejemplo:
String multiline = "Mahidhar\nSophia\nLiam";
[Link]().forEach([Link]::println);

Estos mejoran las capacidades de manipulación de cadenas, haciendo que el código sea
más expresivo y conciso.

Sintaxis de variable local para parámetros lambda.


Permite varsu uso en expresiones lambda. Ejemplo:

var list = [Link]("Mahidhar", "Sophia", "Liam");


[Link]((var name) -> [Link](name));

Esto proporciona coherencia en la declaración de variables, mejorando la legibilidad del


código.

Recolector de basura Epsilon.


Un recolector de basura sin operación que gestiona la asignación de memoria, pero no la
recupera. Útil para pruebas de rendimiento y aplicaciones de corta duración donde la
sobrecarga del GC es un problema.

Ejecutar archivos Javajava


permite ejecutar programas de código fuente de un solo archivo sin compilación.
Ejemplo:

$ java [Link]

Esto simplifica las pruebas y la creación de scripts al reducir la necesidad de pasos de


compilación explícitos.

Java 12: Rendimiento y productividad


Java 12, lanzado en marzo de 2019, introdujo varias mejoras de rendimiento y
productividad. Estas son las 5 características principales de Java 12:

Expresiones Switch (Vista previa)


Mejora las sentencias switch con una nueva sintaxis y nuevas funciones. Ejemplo:

var result = switch (day) {


case MONDAY, FRIDAY, SUNDAY -> "Work day";
case TUESDAY -> "Soccer day";
default -> "Midweek day";
};

Esto simplifica la lógica de conmutación compleja, haciendo que el código sea más
legible y conciso.

API de Constantes JVM:


Introduce una API para modelar descripciones nominales de archivos de clase clave y
artefactos de tiempo de ejecución. Esto facilita el análisis y la transformación de
bytecode, lo que facilita el desarrollo de herramientas y frameworks.

Archivos CDS predeterminados:


Permiten compartir datos de clase de forma predeterminada, lo que mejora el tiempo de
inicio y el consumo de memoria. Esto optimiza el uso de recursos, lo que resulta
especialmente beneficioso en entornos contenedorizados.

Shenandoah GC (Experimental):
Un recolector de basura con tiempos de pausa cortos, diseñado para grandes montones.
Esto reduce los tiempos de pausa de la recolección de basura, mejorando el rendimiento
en aplicaciones con grandes requisitos de memoria.

Microbenchmark Suite
proporciona un conjunto de microbenchmarks para evaluar el rendimiento de diversas
funciones de la JVM. Esto ayuda a los desarrolladores a comprender y optimizar el
rendimiento de sus aplicaciones.

Java 13: Mejoras incrementales


Java 13, lanzado en septiembre de 2019, continuó la tendencia de mejoras
incrementales. Aquí

Estas son las 5 características principales de Java 13:

Bloques de texto (Vista previa)


Simplifica la creación de cadenas multilínea. Ejemplo:

String json = """


{
"name": "Mahidhar",
"age": 30
}
""";

Esto mejora la legibilidad y el mantenimiento del código que involucra cadenas


multilínea.

Expresiones Switch (segunda vista previa)


Refina aún más la función de expresión switch introducida en Java 12. Esto continúa
simplificando y modernizando las declaraciones switch, mejorando la claridad del
código.

Reimplementación de la API de sockets heredada.


Reemplaza la implementación subyacente de la API de sockets heredada para mejorar el
rendimiento y la facilidad de mantenimiento. Esto optimiza el rendimiento y la
fiabilidad de la comunicación de red.
Los archivos CDS dinámicos
amplían el uso compartido de datos de clase a las clases cargadas dinámicamente. Esto
reduce aún más el tiempo de inicio y el uso de memoria al compartir más datos de clase
entre aplicaciones.

ZGC: Liberar memoria no utilizada.


Mejora el recolector de basura Z para devolver la memoria no utilizada al sistema
operativo. Esto optimiza el uso de la memoria, especialmente en entornos con
demandas de memoria fluctuantes.

Java 14: Productividad y sintaxis


Java 14, lanzado en marzo de 2020, se centró en la productividad y las mejoras
sintácticas. Estas son las 5 características principales de Java 14:

Expresiones de conmutación (estándar):


Finaliza la función de expresión de conmutación, convirtiéndola en una función
estándar del lenguaje. Ejemplo:

var result = switch (day) {


case MONDAY, FRIDAY, SUNDAY -> "Work day";
case TUESDAY -> "Soccer day";
default -> "Midweek day";
};

Esto simplifica la lógica de conmutación compleja y mejora la legibilidad del código.

Registros (Vista previa)


Presenta registros, una nueva forma de declarar clases de datos. Ejemplo:

public record Person(String name, int age) {}

Esto reduce el código repetitivo para los portadores de datos, mejorando la simplicidad
y la legibilidad del código.

Coincidencia de patrones para instanceof (Vista previa)


Simplifica las comprobaciones y conversiones de tipos. Ejemplo:

if (obj instanceof String s) {


[Link]([Link]());
}

Esto reduce el código repetitivo, lo que hace que las verificaciones de tipos y las
conversiones sean más concisas y legibles.

Útil: NullPointerExceptions
mejora NullPointerExceptionlos mensajes para incluir detalles sobre lo que sucedió null.
Esto mejora la eficiencia de la depuración al proporcionar mensajes de error más
informativos.
Los búferes de bytes mapeados no volátiles
mejoran el rendimiento y la fiabilidad de las operaciones de E/S de archivos. Esto
optimiza las operaciones de E/S de archivos, especialmente en aplicaciones que
manejan archivos grandes o con altas demandas de E/S.

Java 15: mejoras del lenguaje y del entorno de ejecución


Java 15, lanzado en septiembre de 2020, introdujo varias mejoras en el lenguaje y el
entorno de ejecución. Estas son las 5 características principales de Java 15:

Bloques de texto (estándar)


: Finaliza la función de bloque de texto, convirtiéndola en una función estándar del
lenguaje. Ejemplo:

String json = """


{
"name": "Mahidhar",
"age": 30
}
""";

Esto simplifica la creación y el mantenimiento de cadenas multilínea.

Clases Selladas (Vista Previa)


Permite la declaración de clases selladas, restringiendo qué clases pueden extenderlas o
implementarlas. Ejemplo:

public abstract sealed class Shape


permits Circle, Square, Rectangle {}

Esto proporciona más control sobre las jerarquías de clases, mejorando la seguridad y la
capacidad de mantenimiento del código.

Clases ocultas:
Introduce clases ocultas, pensadas para frameworks que generan clases en tiempo de
ejecución. Esto mejora el rendimiento y la seguridad de las clases generadas en tiempo
de ejecución.

Coincidencia de patrones para instanceof (Segunda vista previa).


Refina aún más la coincidencia de patrones para instanceofla función. Esto simplifica las
comprobaciones y conversiones de tipos, mejorando la legibilidad del código.

ZGC: Descarga Concurrente de Clases.


Mejora el Recolector de Basura Z para descargar clases simultáneamente, lo que mejora
el rendimiento. Esto optimiza el uso de memoria y el rendimiento en aplicaciones con
carga dinámica de clases.

Java 16: Mejora de la productividad y el rendimiento


Java 16, lanzado en marzo de 2021, trajo consigo varias mejoras de productividad y
rendimiento. Estas son las 5 características principales de Java 16:

Registros (Estándar)
Finaliza la función de registro, convirtiéndola en una función estándar del lenguaje.
Ejemplo:

public record Person(String name, int age) {}

Esto reduce el código repetitivo para los portadores de datos, mejorando la simplicidad
y la legibilidad del código.

Coincidencia de patrones para instanceof (Estándar):


Finaliza la coincidencia de patrones para instanceofla característica. Ejemplo:

if (obj instanceof String s) {


[Link]([Link]());
}

Esto reduce el código repetitivo, lo que hace que las verificaciones de tipos y las
conversiones sean más concisas y legibles.

Clases Selladas (Segunda Vista Previa)


Perfecciona aún más la función de clases selladas. Esto continúa brindando mayor
control sobre las jerarquías de clases, mejorando la seguridad y la facilidad de
mantenimiento del código.

API de acceso a memoria externa (Incubator):


Introduce una API para acceder a memoria externa fuera del montón de Java. Esto
facilita el desarrollo de aplicaciones de alto rendimiento y eficientes en el uso de
memoria que interactúan con la memoria nativa.

Herramienta de Empaquetado
: Proporciona una herramienta para empaquetar aplicaciones Java autónomas. Esto
simplifica la distribución e implementación de aplicaciones Java, especialmente en
entornos contenedorizados.

Java 17: Soporte a largo plazo y más


Java 17, lanzada en septiembre de 2021, es una versión con soporte a largo plazo (LTS)
que incorpora numerosas mejoras. Estas son las 5 características principales de Java 17:

Clases Selladas (Estándar)


Finaliza la función de clases selladas, convirtiéndola en una función estándar del
lenguaje. Ejemplo:

public abstract sealed class Shape


permits Circle, Square, Rectangle {}
Esto proporciona más control sobre las jerarquías de clases, mejorando la seguridad y la
capacidad de mantenimiento del código.

Coincidencia de patrones para switch (Vista previa)


Presenta la coincidencia de patrones para sentencias switch. Ejemplo:

switch (obj) {
case String s -> [Link]("String: " + s);
case Integer i -> [Link]("Integer: " + i);
default -> [Link]("Unknown type");
}

Esto simplifica la lógica de conmutación compleja y mejora la legibilidad del código.

API de Funciones Externas y Memoria (Incubadora):


Introduce una API para llamar a funciones nativas y acceder a memoria nativa. Esto
facilita el desarrollo de aplicaciones de alto rendimiento y eficientes en el uso de
memoria que interactúan con código nativo.

Filtros de deserialización específicos del contexto:


Mejoran la seguridad al permitir filtros de deserialización específicos del contexto. Esto
mejora la seguridad al proporcionar un control preciso sobre las operaciones de
deserialización.

Nueva canalización de renderizado para macOS.


Presenta una nueva canalización de renderizado para macOS basada en el framework
Apple Metal. Esto mejora el rendimiento gráfico y la compatibilidad en macOS.

Java 18: Rendimiento y simplicidad


Java 18, lanzado en marzo de 2022, se centró en el rendimiento y la simplicidad. Estas
son las 5 características principales de Java 18:

UTF-8 por defecto:


Establece UTF-8 como el conjunto de caracteres predeterminado para el entorno de
ejecución de Java. Esto garantiza la coherencia en el manejo de datos de caracteres en
diferentes plataformas.

Servidor Web Simple:


Presenta un servidor web simple para prototipado y pruebas. Ejemplo:

jwebserver --port 8080

Esto simplifica el desarrollo y la prueba de aplicaciones web.

Fragmentos de código en la documentación de la API de Java:


Mejora la documentación de la API con fragmentos de código. Esto mejora la usabilidad
y la comprensión de las API de Java.
API vectorial (Tercera Incubadora):
Introduce una API vectorial para expresar cálculos vectoriales. Esto facilita el desarrollo
de aplicaciones de alto rendimiento con datos paralelos.

API de memoria y funciones externas (Segunda incubadora).


Perfecciona aún más la API de memoria y funciones externas. Esto facilita el desarrollo
de aplicaciones de alto rendimiento y eficientes en memoria que interactúan con código
nativo.

Java 19: Rendimiento y productividad mejorados


Java 19, lanzado en septiembre de 2022, trajo consigo varias mejoras de rendimiento y
productividad. Estas son las 5 características principales de Java 19:

Hilos Virtuales (Versión Previa)


Presenta hilos virtuales para concurrencia ligera. Esto simplifica el desarrollo de
aplicaciones de alta concurrencia al reducir la sobrecarga de la gestión de hilos.

Coincidencia de patrones para switch (Segunda vista previa).


Refina aún más la coincidencia de patrones para las sentencias switch. Esto simplifica la
lógica compleja de switch y mejora la legibilidad del código.

Concurrencia Estructurada (Incubadora)


Introduce una API para concurrencia estructurada, lo que facilita la gestión de múltiples
tareas. Esto mejora la fiabilidad y la legibilidad del código al proporcionar un enfoque
estructurado para la concurrencia.

API de memoria y funciones externas (Tercera incubadora):


Perfecciona aún más la API de memoria y funciones externas. Esto facilita el desarrollo
de aplicaciones de alto rendimiento y eficientes en el uso de memoria que interactúan
con código nativo.

El puerto Linux/RISC-V
añade compatibilidad con la arquitectura Linux/RISC-V. Esto amplía la compatibilidad
de la plataforma con Java, haciéndolo disponible en más configuraciones de hardware.

Java 20: mejoras del lenguaje y del rendimiento


Java 20, lanzado en marzo de 2023, se centró en mejoras del lenguaje y el rendimiento.
Estas son las 5 características principales de Java 20:

Hilos Virtuales (Segunda Vista Previa)


Mejora la funcionalidad de hilos virtuales para una concurrencia ligera. Esto simplifica
aún más el desarrollo de aplicaciones de alta concurrencia.

Coincidencia de patrones para switch (Tercera versión preliminar)


Refina aún más la coincidencia de patrones para las sentencias switch. Esto simplifica la
lógica compleja de switch y mejora la legibilidad del código.
Patrones de registro (vista previa)

Introduce patrones de registro para la coincidencia de patrones con registros. Ejemplo:

record Point(int x, int y) {}


switch (obj) {
case Point(var x, var y) -> [Link]("Point: (" + x + ", " + y + ")");
default -> [Link]("Unknown type");
}

Esto simplifica la coincidencia de patrones con clases de registros, mejorando la


legibilidad del código.

API de memoria y funciones externas (Cuarta Incubadora):


Perfecciona aún más la API de memoria y funciones externas. Esto facilita el desarrollo
de aplicaciones de alto rendimiento y eficientes en el uso de memoria que interactúan
con código nativo.

Valores con Alcance (Incubadora)


Introduce valores con alcance para compartir de forma segura y eficiente datos
inmutables dentro y entre subprocesos. Esto mejora la gestión de la concurrencia al
proporcionar un mecanismo para compartir datos a nivel de subproceso.

Java 21: Soporte a largo plazo y más


Java 21, lanzada en septiembre de 2023, es una versión con soporte a largo plazo (LTS)
que incorpora numerosas mejoras. Estas son las 5 características principales de Java 21:

Hilos Virtuales (Estándar)


Finaliza la función de hilos virtuales, convirtiéndola en un modelo de concurrencia
estándar. Esto simplifica el desarrollo de aplicaciones de alta concurrencia al reducir la
sobrecarga de la gestión de hilos.

Coincidencia de patrones para switch (Cuarta vista previa)


Perfecciona aún más la coincidencia de patrones para las sentencias switch. Esto
simplifica la lógica compleja de switch y mejora la legibilidad del código.

Patrones de Registro (Segunda Vista Previa)


Refina aún más los patrones de registro para la coincidencia de patrones con registros.
Esto simplifica aún más la coincidencia de patrones con clases de registro, mejorando la
legibilidad del código.

API de memoria y funciones externas (Quinta Incubadora):


Perfecciona aún más la API de memoria y funciones externas. Esto facilita el desarrollo
de aplicaciones de alto rendimiento y eficientes en el uso de memoria que interactúan
con código nativo.
Colecciones secuenciadas:
Introduce colecciones secuenciadas para proporcionar una interfaz común para
colecciones con un orden de iteración definido. Ejemplo:

SequencedCollection<String> seq = [Link]("Mahidhar", "Sophia",


"Liam");
[Link]([Link]::println);

Esto estandariza el orden de iteración, mejorando la previsibilidad y confiabilidad del


código.

Java 22: Últimas innovaciones


Java 22, lanzado en marzo de 2024, continúa desarrollando los avances de versiones
anteriores. Estas son las 5 características principales de Java 22:

Coincidencia de patrones mejorada:


amplía la coincidencia de patrones a más contextos, lo que proporciona mayor
flexibilidad y expresividad. Ejemplo:

switch (obj) {
case String s && [Link]() > 5 -> [Link]("Long string: " + s);
case Integer i && i > 10 -> [Link]("Large number: " + i);
default -> [Link]("Other type");
}

Esto mejora la legibilidad y la capacidad de mantenimiento del código al permitir


condiciones más complejas en la coincidencia de patrones.

Genéricos Universales:
Introduce genéricos universales para simplificar las declaraciones de tipos genéricos y
mejorar la seguridad de tipos. Ejemplo:

class Box<T extends Number> {


private T value;
public Box(T value) {
[Link] = value;
}
public T getValue() {
return value;
}
}

Esto mejora la reutilización del código y la seguridad de tipos al permitir declaraciones


de tipos genéricos más flexibles y potentes.

Manejo mejorado de JSON:


Mejora las capacidades de manejo de JSON con nuevas API y mejoras en las existentes.
Ejemplo:
JsonObject json = [Link]()
.add("name", "Mahidhar")
.add("age", 30)
.build();
[Link]([Link]());

Esto simplifica el trabajo con datos JSON, haciéndolo más eficiente e intuitivo.

Recolección de basura optimizada:


Introduce optimizaciones adicionales en la recolección de basura, reduciendo la latencia
y mejorando el rendimiento. Esto mejora la capacidad de respuesta y la estabilidad de
las aplicaciones, especialmente en escenarios con uso intensivo de memoria.

Funciones de red avanzadas:


Añade funciones de red avanzadas para admitir los protocolos más recientes y mejorar
el rendimiento de la red. Ejemplo:

HttpClient client = [Link]()


.version([Link].HTTP_2)
.build();
HttpRequest request = [Link]()
.uri([Link]("[Link]
.build();
HttpResponse<String> response = [Link](request,
[Link]());
[Link]([Link]());

Esto respalda los estándares de redes modernos, mejorando el rendimiento y la


interoperabilidad de las aplicaciones.

Migrar a Java 17 o superior puede traer beneficios significativos, pero también conlleva
desafíos. Estos son los 5 problemas principales que podrías encontrar:

1. Problemas de compatibilidad
• Descripción : Es posible que algunas bibliotecas y frameworks no sean
totalmente compatibles con Java 17 o versiones posteriores. Esto es
especialmente relevante para dependencias antiguas o con menor
mantenimiento.
• Ejemplo : si está utilizando una versión anterior de una biblioteca que depende
de API obsoletas o eliminadas, puede encontrar errores de compilación o de
tiempo de ejecución.
• Solución : actualice todas las dependencias a sus últimas versiones y verifique la
compatibilidad con Java 17. Utilice herramientas como jdepspara analizar sus
dependencias e identificar posibles problemas.

2. API eliminadas y obsoletas


• Descripción : Java 17 y versiones posteriores han eliminado varias API y
funciones obsoletas en versiones anteriores. Esto puede causar errores de
compilación si el código depende de estas API.
• Ejemplo : La eliminación de la AppletsAPI Security Managerpuede afectar a las
aplicaciones que aún utilizan estas funciones.
• Solución : Refactorice su código para reemplazar las API obsoletas con sus
equivalentes modernos. Revise las notas de la versión de cada versión de Java
para comprender qué funciones se han eliminado y planificar en consecuencia.

3. Sistema de módulos (JPMS)


• Descripción : La introducción del Sistema de Módulos de Plataforma Java (JPMS)
en Java 9 puede complicar el proceso de migración, especialmente para
aplicaciones grandes y monolíticas.
• Ejemplo : Dividir una base de código grande en módulos y garantizar que las
dependencias se gestionen adecuadamente puede ser un desafío.
• Solución : Empieza por modularizar las partes más críticas de tu aplicación y
refactoriza gradualmente el resto. Úsalo jdepspara identificar las dependencias de
los módulos y crear descriptores de módulos ( [Link]).

4. Nuevo comportamiento de recolección de basura


• Descripción : Los cambios y mejoras en los algoritmos de recolección de basura
(GC) en las versiones más nuevas de Java pueden afectar el rendimiento y el
comportamiento de las aplicaciones.
• Ejemplo : El recolector de basura G1 se convirtió en el predeterminado en Java
9, y Java 17 incluye mejoras adicionales. Las aplicaciones con ajustes de
rendimiento específicos para recolectores de basura antiguos podrían necesitar
un reajuste.
• Solución : Pruebe su aplicación exhaustivamente con la nueva configuración del
GC. Perfile y monitoree el rendimiento, y ajuste los parámetros de ajuste del GC
según sea necesario.

5. Cambios en el lenguaje y la sintaxis de Java


• Descripción : Las nuevas características del lenguaje y los cambios de sintaxis
introducidos en Java 17 y superiores, como registros, clases selladas y
coincidencia de patrones, pueden requerir una refactorización significativa de su
base de código.
• Ejemplo : la adopción de nuevas características, como registros y coincidencia
de patrones, podría requerir cambios en las clases de datos y en la forma en que
maneja ciertas operaciones.
• Solución : Planifique la adopción gradual de nuevas funciones del lenguaje.
Asegúrese de que su equipo de desarrollo esté familiarizado con las últimas
mejoras de Java y proporcione capacitación si es necesario.
¿Que es un Java Stream?
04/01/2021 Por Cecilio Álvarez Caules 16 comentarios

En muchas ocasiones me realizan preguntas sobre el concepto de Java


Stream ya que a veces es difícil entender su funcionamiento y la
relación que tienen con un ArrayList clásico. Vamos a verlo a detalle a
partir de del concepto de Factura que contiene concepto e importe :

Veamos un ejemplo en código del uso de un Stream:

CURSO JAVA 8
GRATIS
APUNTATE!!

package [Link];
import [Link];
import [Link];
public class Principal {
public static void main(String[] args) {
Factura f= new Factura("ordenador",1000);
Factura f2= new Factura("movil",300);
Factura f3= new Factura("impresora",200);
Factura f4= new Factura("imac",1500);
List<Factura> lista= new ArrayList<Factura>();
[Link](f);
[Link](f2);
[Link](f3);
[Link](f4);
Factura facturaFiltro=
[Link]()
.filter(elemento->[Link]()->300)
.findFirst()
.get();
[Link]([Link]());
}
}
El ejemplo es relativamente sencillo . Creamos un Stream a partir de
una lista de Facturas y usamos la operación de filter dentro del Java
Stream para quedarnos con aquellas facturas que cumplen la condición
en este caso solo una de ellas cumple :

La mayoría de los desarrolladores pensarán que recorreremos la lista


completa de facturas que luego se realiza la operación de filtrado para
finalmente obtener un único elemento.
Java Stream y flujos de trabajo
Aunque este parece el comportamiento correcto, realmente no lo es y
para comprobarlo basta con cambiar el código y añadir nosotros
nuestro propio predicado con la condición .

¡Apúntate ahora!
Cursos desde $9.99 al mes. ¡Prueba
una semana gratis!
¡SUSCRIBIRSE!

package [Link];
import [Link];
import [Link];
import [Link];
public class Principal2 {
public static void main(String[] args) {
Factura f= new Factura("ordenador",1200);
Factura f2= new Factura("movil",300);
Factura f3= new Factura("impresora",200);
Factura f4= new Factura("imac",1500);
List<Factura> lista= new ArrayList<Factura>();
[Link](f);
[Link](f2);
[Link](f3);
[Link](f4);
Predicate<Factura> predicado= new Predicate<Factura>() {
@Override
public boolean test(Factura f) {
[Link](" iteracion ");
return [Link]()>300;
}
};
Factura facturaFiltro=
[Link]()
.filter(predicado).findFirst().get();
[Link]("UNICA :"+[Link]());
}
}
En este caso el predicado ejecuta la misma condición pero ademas
imprime la iteración por pantalla .Deberíamos recibir varias iteraciones
ya que tenemos 4 elementos . Sin embargo el resultado es muy muy
curioso ya que solo se imprime una iteración por la consola.

¿Qué paso con el resto de iteraciones? ¿Cómo funciona un Java


Stream?

Java Stream y su comportamiento


Esta es parte difícil de entender del concepto de Stream. Los Streams
diseñan un flujo de trabajo que se ejecuta de forma unitaria item a
item.

En este caso es evidente que el flujo de trabajo es: busca el primer


elemento cuyo importe supere los 300 euros y retórnalo. Una vez
encontrado no es necesario recorrer el resto de la lista , de hecho es
un error hacerlo . Por lo tanto los streams simplemente ejecutan el
flujo de trabajo para el primer elemento y como este cumple los
requisitos el flujo termina , así de sencillo.

Streams y Rendimiento
Esto nos permitirá una mejora en el rendimiento ya que no es necesario
recorrer la lista completa.
Queries en Azure DevOps
Si has visto un código parecido a este:
[Link]([Link]::println);
y no tienes idea de qué se trata, o viéndolo de reojo piensas “tal
vez hace esto”, pero no estás totalmente seguro, sigue leyendo
este post que de seguro te aclarará algunas dudas. El código
anterior es un ejemplo de programación funcional en Java, que se
encuentra disponible desde su octava versión. Dicho tipo de
programación consiste, más que nada, en ahorrar tiempo y líneas
de código, además de facilitar el entendimiento de un código. Para
programar de esta forma usamos dos tipos de funciones, por una
parte, las Lambdas y, por otra, las Method reference operator o
también conocidas como Double colon (::). A continuación, te
mostramos sus estructuras:

Lambdas
Consisten en tres partes: los parámetros, arrow operator (->) (en
este caso el guion y el signo mayor, que deben estar seguidos, no
pueden tener espacios entre sí), y el cuerpo de la función.
() -> [Link]("Hello Functional World")
En este caso, los parámetros son nulos, por lo tanto, es necesario
usar los paréntesis vacíos, agregar el arrow operator (->) y,
finalmente, el cuerpo de la función que, en este caso, imprimirá el
mensaje en la consola. En el caso de que tenga un solo parámetro
se pueden obviar los paréntesis, a menos que se indique el tipo de
la variable.
a -> [Link](a)
(String a) -> [Link](a)
Si existe más de un parámetro los paréntesis son necesarios. En
todos los casos indicados, el cuerpo solo cumple una sentencia,
por esto no es necesario que termine en punto y coma (;). Pero, si
tuviera más de una, sería necesaria la siguiente estructura:
a->{
[Link](a);
[Link]([Link]());
}

Si fuese necesario retornar algo y es una función de una sola


sentencia, no es necesario especificar el return.
a -> a+1
a -> { return a+1; }
En ambos casos, las líneas de código hacen lo mismo, retornando
el valor a + 1, por lo que cumplen la misma función.

Double Colon (::)


Esta funciona de manera muy parecida a las Lambdas, sin
embargo, el Double colon se utilizará en lugar de Lambdas en el
caso de que la función tenga solo una sentencia y que dicha
sentencia sea un método estático, un constructor o un método de
una instancia. En este caso, los parámetros se obvian y solo se
llama a la función
a -> [Link](a)
[Link]::println
Estas dos cumplen el mismo fin.

Ejemplo:

Digamos que tenemos un array de Strings y quisiéramos mostrar


en consola cada uno de los valores, con la programación funcional
nos ahorraríamos algunas líneas:
ArrayList<String> names =
[Link](
"John", "John", "Mariam", "Alex", "Mohammado",
"Mohammado", "Vincent", "Alex", "Alex");
Antes de la programación funcional se hubiese tenido que hacer de
la siguiente manera:
for (String name : names) {
[Link](name);
}
Ahora sería algo así:
[Link](name->[Link](name));
Incluso podría acortarse aún más, así:
[Link]([Link]::println);
El resultado de lo anterior sería algo como esto:
John
John
Mariam
Alex
Mohammado
Mohammado
Vincent
Alex
Alex

API Stream
Stream es una librería que facilita mucho el trabajo con collections,
como List y Sets, y consiste en solo llamar un método.
[Link]()
Luego de esto se llama al resto de las funciones. Ahora hablaré de
aquellas que considero más útiles.
Filter
Esta funcion es muy útil para filtrar acorde a una condición, la cual
tiene que ser de tipo boolean. Siguiendo con el ejemplo de los
nombres, digamos que necesitamos solo los nombres que
contengan la letra “o”:
[Link]().filter(name->
[Link]("o")).forEach([Link]::println);
Dando como resultado:
John
John
Mohammado
Mohammado

Map
Este método es útil para cuando se quiere cambiar el tipo de
respuesta, como convertir algún tipo instancia de una clase a otra.
Digamos que se tiene la clase person y la clase persona
public class Person {
Integer id;
String firstName;
String lastName;
String email;
String gender;
Integer age;

// Getter y Setter o use Lombok :)

};
public class Persona {
Integer id;
String nombre;
String apellido;
String correo;
String genero;
Integer edad;

// Getter y Setter o use Lombok :)


};
En sí son la misma clase, solo que una tiene los atributos en ingles
y otra en español. En el caso de que tengas una lista de person
(para el ejemplo se llamara people) y necesitas una lista de
persona sería algo como esto:
List<Persona> personas =
[Link]()
.map(
person -> {
Persona persona = new Persona();

[Link]([Link]());
[Link]([Link]());
/*
resto de los setters
*/
return persona;
})
.collect([Link]());
O digamos que, de la lista de nombres del ejemplo anterior,
quisieras tener un arreglo (array) del largo (length) de cada
nombre:
[Link]()
.map(name -> [Link]())
.collect([Link]());
Aquí sin afectar al array de names utilizado en el ejemplo anterior,
se puede obtener otro arreglo (array) con el respectivo largo
(lenght) de cada nombre.
Sorted
Esta función, cumple el rol de ordenar el arreglo (array) acorde a
una regla de comparación.
[Link]()
.sorted([Link]())
.forEach([Link]::println);
En este caso usamos Comparator, que tiene varios métodos que
nos ayudan a comparar dos objetos, por ejemplo, naturalOrder, que
en este caso ordenaría los nombres en orden alfabético. También
podemos usar reverseOrder que ordenará los nombres en sentido
contrario. El output final sería:
Alex
Alex
Alex
John
John
Mariam
Mohammado
Mohammado
Vincent
Pero digamos que también quisiéramos hacer otro tipo de
comparación, por ejemplo, con el largo del nombre. En este caso
podríamos llamar a la función comparing que toma como parámetro
una lambda.
[Link]()
.sorted([Link](String::length))
.forEach([Link]::println);
Lo anterior daría una respuesta como esta:
John
John
Alex
Alex
Alex
Mariam
Vincent
Mohammado
Mohammado
Por último, en caso de que deseara agregar n filtros más, por
ejemplo, quisiera ordenar los nombres por su largo y luego
ordenarlos por orden alfabético, entonces, primero compararía el
largo del String y luego, en el caso de que los String fuesen de
igual largo, compararía en orden alfabético. Para hacer esto, se
tendría que hacer un llamado a thenComparing:
[Link]()
.sorted( Comparator
.comparing(String::length)
.thenComparing([Link]()) )
.forEach([Link]::println);

El resultado sería así:


Alex
Alex
Alex
John
John
Mariam
Vincent
Mohammado
Mohammado
thenComparing funciona para agregar mas filtros y se puede usar n
cantidad de
veces(comparing().thenComparing().thenComparing()…thenCompa
ring()), dejando prioridad a los filtros anteriores, como en el caso
anterior se le dio prioridad al largo del String, y luego se
compararon según el orden natural, es importante que se la
primera comparación sea solo comparing y luego llamar a
thenComparing, sino no compilaría.
Distinct
Distinct ayuda a evitar datos duplicados y su uso es muy sencillo.
Digamos que en el ejemplo anterior queremos ver los nombres sin
que estos se repitan:
[Link]()
.distinct()
.forEach([Link]::println);
El resultado se mostraría así:
John
Mariam
Alex
Mohammado
Vincent

Limit
La función Limit ayuda a reducir el tamaño del arreglo (array) al
número de registros que necesites:
[Link]()
.limit(5)
.forEach([Link]::println);
El resultado se mostraría así:
John
John
Mariam
Alex
Mohammado
Funciones Terminales
Stream funciona con algo llamado lazy evaluation, que consiste en
que Java revisa si la función que estás haciendo luego será
utilizada para algo o simplemente será ignorada. En el caso de que
sea ignorada el código de stream no se ejecutará. Por ejemplo, en
los casos anteriores, de no haber agregado forEach(), no se habría
ejecutado el sorted, el limit ni el resto de los ejemplos, porque
forEach trabaja como función terminal. Para este artículo, hablaré
solo de cuatro funciones terminales:

ForEach
Esta función ejecuta una sentencia por cada uno de los valores de
los arreglos (arrays), pero es necesario comentar que esta función
no tiene ningún valor de retorno porque en los ejemplos anteriores
siempre se utilizó [Link]().

Count
Esta función es similar al size() de un arrayList, entrega la cantidad
de valores que hay en la lista:
int cantidad = [Link]().limit(5).count();

Reduce
Esta función permite tomar todos los valores y almacenarlos en una
sola variable, digamos que en el ejemplo de los nombres quisiera
dejar un solo String con todos los nombres concatenados; con
reduce haría algo así:
String reduce = [Link]().reduce("", String::concat);
El resultado sería:
JohnJohnMariamAlexMohammadoMohammadoVincentAlexAlex
Es importante destacar que el primer parámetro del reduce cuenta
como el inicio del nuevo String y luego concatenará todos los
nombres. También es bastante útil para datos numéricos, como
para saber la suma de todos estos.
Integer[] integers = {1, 2, 3, 4, 5};
Integer reduce = [Link](integers).reduce(0, Integer::sum);
Lo que daría como resultado la cantidad de 15.

Collect
Por último, esta función permite guardar el stream como un
collection o map.
List<String> collect = [Link]()
.filter(s -> [Link]("o"))
.collect([Link]());

[Link]("sin filtro");
[Link](names);
[Link]("con filtro");
[Link](collect);
Resultando así:
* sin filtro
[John, John, Mariam, Alex, Mohammado, Mohammado, Vincent,
Alex, Alex]
* con filtro
[John, John, Mohammado, Mohammado]
En el caso de que quisiéramos contar cuántas veces se repite un
nombre, se podría hacer un map de la siguiente manera:
Map<String, Long> counting =
[Link]().collect([Link]([Link](),
[Link]()));
[Link](counting);
Retornando así:
{Alex=3, Vincent=1, Mariam=1, Mohammado=2, John=2}

Comparación Funcional contra


Imperativa
Aunque la programación funcional es bastante nueva en Java, eso
no quiere decir que es siempre lo mejor, puesto que hay casos en
que la programación imperativa (old school java) funciona mejor
que la programación funcional, más que nada es un rendimiento
contra mantenibilidad, dado que la programación imperativa tiende
a tener mejores tiempos de respuesta con respecto al rendimiento,
mientras que la funcional es mucho mas mantenible y dejar el
código más limpio, veamos las siguiente comparaciones, donde se
comparan los tiempos de respuestas en milisegundos: Se desea
calcular el máximo en un arreglo de Integers el cual cuenta con
2.000.000 agregados aleatoriamente:
int size = 2_000_000;
List<Integer> integers = null;

@Setup
public void setup() {
integers = new ArrayList<>(size);
populate(integers);
}

public void populate(List<Integer> list) {


Random random = new Random();
for (int i = 0; i < size; i++) {
[Link]([Link](1000000));
}
}
Los cuales se implementan de las siguientes maneras:

• iterator
public int iteratorMaxInteger() {
int max = Integer.MIN_VALUE;
for (Iterator<Integer> it = [Link](); [Link](); ) {
max = [Link](max, [Link]());
}
return max;
}

• forEach
public int forEachLoopMaxInteger() {
int max = Integer.MIN_VALUE;
for (Integer n : integers) {
max = [Link](max, n);
}
return max;
}

• fori
public int forMaxInteger() {
int max = Integer.MIN_VALUE;
for (int i = 0; i < size; i++) {
max = [Link](max, [Link](i));
}
return max;
}

• stream
public int streamMaxInteger() {
Optional<Integer> max = [Link]().reduce(Integer::max);
return [Link]();
}
Como vemos es un caso bastante sencillo, al cual se le generan los
siquientes tiempos:
FunciónTiempo
(ms)iterator3,150forEach3,163fori4,936stream12,491
Evidentemente el iterator es el mas rapido y el stream el más lento.
Ahora se generarán filtros para el mismo arreglo de la siguiente
manera:
• iterator
public List<Integer> filtroIterator() {
List<Integer> filtrados = new ArrayList<>();
for (Iterator<Integer> it = [Link](); [Link](); ) {
Integer integer = [Link]();
if (integer < 10000) {
if (integer % 2 == 0) {
if (integer % 3 == 0) {
if (integer % 5 == 0) {
[Link](integer);
}
}
}
}
}
return filtrados;
}

• forEach
public List<Integer> filtroForEach() {
List<Integer> filtrados = new ArrayList<>();
for (Integer integer : integers) {
if (integer < 10000) {
if (integer % 2 == 0) {
if (integer % 3 == 0) {
if (integer % 5 == 0) {
[Link](integer);
}
}
}
}
}
return filtrados;
}
• fori
public List<Integer> filtroFori() {
List<Integer> filtrados = new ArrayList<>();
for (int i = 0; i < [Link](); i++) {
Integer integer = [Link](i);
if (integer < 10000) {
if (integer % 2 == 0) {
if (integer % 3 == 0) {
if (integer % 5 == 0) {
[Link](integer);
}
}
}
}
}
return filtrados;
}
• stream
public List<Integer> filtroStream() {
return [Link]()
.filter(integer -> integer < 10000)
.filter(integer -> integer % 2 == 0)
.filter(integer -> integer % 3 == 0)
.filter(integer -> integer % 5 == 0)
.collect([Link]());
}
Generando los siguientes tiempos:
FunciónTiempo
(ms)iterator5,784forEach6,259fori6,684stream5,164
Aquí por otro lado el más rápido es el stream.
Estos dos benachmarks nos dan a entender que la "lazy
evaluation" que se comentó en las funciones terminales ayuda
bastante al rendimiento de stream, en especial cuando de mas
filtros se trata, por eso se recomienda que al usar streams la
primera operación sea filter(). El código base de este benchmark se
tomó de: [Link]
En síntesis, es como comparar 2*2*2*2 contra 2^4, en ambos
casos el resultado es 16, sin embargo, puede que la multiplicación
tome más tiempo de escritura, versus al exponente que significa
una expresión más corta, pero que quizá implica más tiempo de
cálculo. Es decir, evidentemente, no vale la pena escribir 2^2,
puesto que 2*2 es más óptimo, pero sí vale la pena usar 2^64 para
ahorrar tiempo.
Processing data sequences without numerous for-loops in Java is
possible with Stream API that allows for handling data using
functional-style operations.

This tutorial will guide you through key concepts of Java Stream
API, how to create streams and process data using various
operations, and how to use Stream Gatherers, a powerful
addition to Stream API in JDK 22 for creating custom operations.

Subscribe to our newsletter


Read the industry news, receive solutions to your problems, and find the ways to
save money.

Work e-mail *

Subscribe

I agree with Terms and Privacy *


* This field is required
Table of Contents
1. Introduction to Java streams
2. Core concepts of Java streams
3. Creating streams
1. Finite streams
2. Infinite streams
3. Parallel streams
4. Intermediate operations with streams
1. filter()
2. map()
3. flatMap()
4. sorted()
5. distinct()
6. skip()
7. limit()
5. Custom intermediate operations with Stream Gatherers
6. Terminal operations with streams
1. collect()
2. forEach()
3. reduce()
4. count() / average() / min() / max()
5. findFirst() / findAny()
6. allMatch() / anyMatch() / noneMatch()
7. Dealing with null in streams
8. Conclusion

Introduction to Java
streams
Stream API was introduced in Java 8 to equip developers with a
way of processing collections of elements using the functional
programming approach. If we wanted to define functional
programming in broad strokes, we would say that functional
programming, being the type of declarative programming,
describes the desired results we want the program to achieve
rather than a sequence of steps the program needs to go through
to get these results. The latter is the domain of imperative
programming.

So, in functional programming, we describe or use a ready


function that we want to apply to an element. We can also give
hints to the API how to control element selection from the data
source, but that’s it: the method of looping over the elements,
the arrangement of the source, and other complex tasks like
parallelism are left under the hood.

To sum up, declarative programming tells the program what


needs to be done, and imperative programming — how it needs
to be done.

Therefore, Stream API enables the creation of streams that


consist of elements of a provided collection and functional-style
operations that need to be applied to each of these elements
without affecting the data source. And lambdas and method
references provide a convenient way of using functions with
streams.

Using Stream API makes code more laconic and prevents


modification of the data source.

Core concepts of Java


streams
A stream is a sequence of elements that acts as a data wrapper
for a source of elements. A stream allows us to perform multiple
operations on the elements of a data source without modifying
the original data source. These operations are combined into a
stream pipeline.

A stream pipeline consists of a source, zero or more Intermediate


operations, and a terminal operation:

• A source can be an array, a collection, an I/O channel, a


generator of an infinite sequence of elements, etc.
• Intermediate operations transform a stream into another
stream and support multiple ways of processing data:
you can filter, transform the elements, order them, and
so on.
• Terminal operations yield a result or a side effect: for
instance, you can count elements, collect them, find a
particular element, or perform an action on each element
with forEach.
A stream pipeline

There are two important characteristics of streams to be


considered. Streams are lazy, meaning that intermediate
operations won’t be executed until a terminal operation is
invoked. Also, after executing the terminal operation, the stream
closes automatically, and it won’t be possible to reuse it.

Creating streams
You can create a stream from multiple sources, including
collections, arrays, files, functions, static factory methods, etc. In
addition, streams can be finite with a predefined number of
elements, infinite with a potentially unbound number of elements,
and parallel enabling parallel execution on multiple cores.

Let’s look at all of that in more detail below.

Finite streams
We can obtain a stream from a collection with
a stream() method:
List<String> countries =
[Link]("Germany", "France", "Italy");
Stream<String> countriesStream =
[Link]();
[Link]([Link]::println);
We will discuss the forEach() method below, but for now, it is a
terminal operation that enables us to iterate over elements and in
this case, print them out.
To obtain a stream from an array, we use [Link](). If
you want to get a stream of primitives such as int, long, or
double, you can use the dedicated
interfaces IntStream, LongStream, or DoubleStream:
int[] numbers = {1, 2, 3, 4};
IntStream numbersStream =
[Link](numbers);
[Link]([Link]::println);
Obtaining a stream from a file is possible with [Link]():
try (Stream<String> fileLinesStream =
[Link]([Link]("path/to/file"))) {

[Link]([Link]::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
You can also obtain a stream with a static factory method:

DoubleStream doubleStream = [Link](4.5, 6.7, 1.2);

[Link]([Link]::println);

Infinite streams
Infinite streams can be created using a generate() or
an iterate() method. In addition, you should set the condition
to stop the processing of elements at some point, or else, the
program will run indefinitely. It can be done with
a limit() method, for instance.
With that in mind, let’s create an unbound sequence of random
numbers and limit them to 10 elements:

Random random = new Random();


IntStream randomIntsStream =
[Link](random::nextInt)
.limit(10);
[Link]([Link]::println);
In the example above, we created a stream pipeline by unifying
the generate() and limit() methods. All operations that you
perform on a stream can be merged into a single pipeline, so we
can shrink the snippet above:
Random random = new Random();

[Link](random::nextInt)
.limit(10)
.forEach([Link]::println);

Parallel streams
You can create parallel streams to divide a bulk of work between
several threads. Under the hood,parallel streams use the fork-join
framework that splits the task into several subtasks accomplished
by worker threads. The results are then merged into a single
result.

To create parallel streams, you can use a parallel() method on


a sequential stream:
List<Integer> nums = [Link](1, 2, 3, 4,
5);
int sum = [Link]()
.parallel()
.map(i -> i + 1)
.reduce(0, Integer::sum);
Or a parallelStream() method:
List<Integer> nums = [Link](1, 2, 3, 4,
5);
int sum = [Link]()
.map(i -> i + 1) //this method adds 1
to each element
.reduce(0, Integer::sum); //this method
sums all elements
Note that using parallel streams is not always beneficial for
performance. You can find a good comparison of parallel vs
sequential streams in this article. But as a rule, the more
elements you have to process and the less computation applied to
each element, the bigger the performance gains with parallel
streams.

Intermediate operations
with streams
You have already seen some intermediate operations in action in
sections above, let’s look at them and some other common
operations in more detail.

filter()
The filter() method helps us to filter the elements by a specific
parameter represented by a boolean condition. Only the elements
that match the requirement will be sent down the pipeline for
further processing. For instance, let’s find and print out all even
numbers:
List<Integer> numbers = [Link](4, 7, 3,
8, 1, 2);
[Link]()
.filter(n -> n % 2 == 0)
.forEach([Link]::println);

map()
The map() method accepts a stream of elements, performs some
operation on each element, and returns a stream of modified
elements. Schematically, it looks like that:
Where T is the type of incoming elements, and A is the type of
outcoming elements.
But this operation doesn’t change the elements of the source,
guaranteeing the immutability of the original.

For instance, let’s convert all String elements to lowercase:

List<String> words = [Link]("Apple",


"Banana", "Pear");
[Link]()
.map(String::toLowerCase)
.forEach([Link]::println);

flatMap()
The flatMap() method enables us to obtain a stream for each
element of the incoming stream, and then unify these streams
into one.

For instance, we have two classes, Order and Product:


static class Order {
List<Product> products;
double totalPrice;

public Order(List<Product> products) {


[Link] = products;
calculatetotalPrice(products);
}

private void calculatetotalPrice(List<Product>


products) {
for(Product product : products) {
totalPrice += [Link]();
}
}
public List<Product> getProducts() {
return products;
}
public double getTotalPrice() {
return totalPrice;
}

record Product (String name, int price) {


}
Note the calculatetotalPrice() method: I will show you how
to use a stream instead of a for-loop later on.
Having a List of orders, we can obtain a stream of products for
each order and then combine them into one stream of products
and perform some action on each product (get a name, for
instance):

Product chair = new Product("Chair", 150);


Product table = new Product("Table", 350);
Product pencil = new Product("Pencil", 2);

Order firstOrder = new


Order([Link](chair, table));
Order secondOrder = new
Order([Link](pencil, chair));

List<Order> orders = [Link](firstOrder,


secondOrder);
[Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.forEach([Link]::println);
sorted()
The sorted() method enables us to sort the elements of the
stream. Let’s take a look at the example above: we have a
stream of Strings, and we can simply add sorted() without
parameters to sort them in natural (here, alphabetical) order, or
we can use [Link]() in this method to sort
the elements in descending order:
[Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.sorted([Link]())
.forEach([Link]::println);

distinct()
Let’s continue with orders and products. Both orders include the
same product Chair, so the output of the examples above will
contain a repeating String. To get rid of duplicate elements, we
can use the distinct() method:
[Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.distinct()
.sorted([Link]())
.forEach([Link]::println);

skip()
The skip() method enables us to skip first N elements of the
stream:
List<Integer> numbers = [Link](4, 7, 3,
8, 1, 2);
[Link]()
.skip(2)
.forEach([Link]::println);

limit()
We have already seen the limit() method in action above, when
we created an infinite stream. But you can, of course, use it with
finite streams as well to limit the number of elements:
List<Integer> numbers = [Link](4, 7, 3,
8, 1, 2);
[Link]()
.limit(4)
.forEach([Link]::println);

Custom intermediate
operations with Stream
Gatherers
Stream API provides a limited albeit rich set of intermediate
operations. Sometimes, you need to apply custom logic not
supported by Java streams. Luckily, Java 22 introduced a solution
to that issue: Stream Gatherers.
A new intermediate operation Stream::gather(Gatherer) allows
us to process elements of a stream by applying user-defined logic
in a gatherer, which is an instance of a Gatherer interface.
A gatherer is defined by four functions, some of which are
optional:

• Initializer (optional) provides an object with a private


state, which is useful when a gatherer needs to compare
new elements to the previous ones;
• Integrator (obligatory) integrates a new element from an
input stream. It can also inspect the private state object
and emit elements to the output stream;
• Combiner (optional) can evaluate elements in parallel if
you use parallel streams;
• Finisher (optional) is invoked when there are no more
input elements to consume.
You can create your own gatherers by implementing
the Gatherer interface or take advantage of built-in gatherers
from the Gatherers class.
Let’s see how we can implement gatherers in practice. Note that
Stream Gatherers are still in preview, so you need to enable
preview language features with a command line --enable-
preview flag or select Java 23 Preview features in IntelliJ IDEA
via Project Structure → Project → Language Level.
First, let’s implement a custom gatherer that enables us to collect
unique users by country. Create a simple record User:
record User (String username, String country) { }
Now, we need to create a DistinctByCountryGatherer class that
implements the Gatherer interface:
public class DistinctByCountryGatherer<User, String>
implements Gatherer<User, Set<String>, User> { }
The Gatherer<T,A,R> interface has three parameters, where:
• T is a type of an input element. In our case, it will be an
object of class User.
• A is a type of a gatherer’s private state object that can
be used to track the previously seen elements. In our
case, it’s a Set<String> representing a country.
• R is a type of an output element. In our case, it is the
same as the input parameter.
The DistinctByCountryGatherer has two type
parameters, User and String.
Next, we need a function that will be applied to the elements of a
stream:

private final Function<User, String> selector;

public DistinctByCountryGatherer(Function<User,
String> selector) {
[Link] = selector;
}
We also need to override and update two
methods, initializer() and integrator():
@Override
public Supplier<Set<String>> initializer() {
return HashSet::new;
}

@Override
public Integrator<Set<String>, User, User>
integrator() {
return [Link]((set, user,
downstream) -> {
String extractedCountry =
[Link](user);
if(![Link](extractedCountry)) {
[Link](extractedCountry);
[Link](user);
}

return true;
});
}
As mentioned above, the initializer() function provides a
private state object for the gatherer ro compare elements. We
will store country names in a Set to identify unique ones.
The integrator() function is created by calling
the [Link]() method, which takes a lambda
expression as an argument with three parameters:
• set is is our private state object,
• user is the next input element,
• downstream is an object
of [Link]<T> that pushes its argument
down the stream pipeline.
We extract the country name from the User object by applying
the selector function to it. If our private state object HashSet
doesn’t contain this country name, it is added to the stream, and
the User object is pushed down the pipeline. Otherwise, the
object is discarded.
Let’s use our custom Gatherer in a pipeline:

List<User> users = [Link](


new User("francesca", "Italy"),
new User("mark", "Germany"),
new User("paolo", "Italy"));

[Link]()
.gather(new
DistinctByCountryGatherer<>(User::country))
.forEach([Link]::println);
The output will be:

User[username=francesca, country=Italy]
User[username=mark, country=Germany]
You can combine multiple custom gatherers in one stream
pipeline, creating tailor-made element processing.

Terminal operations with


streams
We had to use some terminal operation above such
as forEach() and reduce() simply because our intermediate
wouldn’t execute without them. But it’s time to familiarize
ourselves with more terminal operations!

collect()
The collect() method enables us to accumulate the elements of
the stream into a collection or a mutable result container (for
instance, you can concatenate all Strings into one String). Let’s
take the example with orders and instead of printing out the
results, collect them into a Set:
Set<String> products = [Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.collect([Link]());
In the case you want to gather the results into a List, you can
simplify the code and use toList() instead
of collect([Link]()):
List<String> products = [Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.distinct()
.sorted([Link]())
.toList();

forEach()
The forEach() method iterates over the elements and performs
a specified action on each. We used it to print out the elements of
the stream, which is the most common operation with this
method.

reduce()
The reduce() method is the reduction operation meaning that it
produces a single result for a stream of elements. There are three
concepts associated with reduce() we need to understand:
• Identity is the initial value of the operation and the
default value in case the stream is empty;
• Accumulation function takes a partial result of the
operation and the next element of a stream;
• Combining function combines the partial result in case of
a parallelized reduction operation.
So, these are signatures for the reduce() method:
• reduce(BinaryOperator<T> accumulator) accepts the
accumulation function and returns an Optional<T>;
• reduce(T identity, BinaryOperator<T>
accumulator) accepts the provided identity and an
accumulation function and return a single value;
• reduce(U identity, BiFunction<U,? super T,U>
accumulator, BinaryOperator<U> combiner) accepts
the provided identity, an accumulation and combining
functions and returns a <U> U.
Let’s look at the example. Previously, we used a for-loop to
calculate a total price for an order.

Instead of iterating over each element manually, we can create a


stream of Integers (product prices) and obtain the sum of all
elements:

private void calculatetotalPrice(List<Product>


products) {
totalPrice = [Link]()
.map(pr -> [Link])
.reduce(0, (x, y) -> x + y);
}
Here, value 0 is the identity that serves as the initial value of the
operation, the lambda expression (x, y) -> x + y is the
accumulation function, where x is the partial result and y is the
next element of the stream.
You can simplify the code above by using the method reference:

private void calculatetotalPrice(List<Product>


products) {
totalPrice = [Link]()
.map(pr -> [Link])
.reduce(0, Integer::sum);
}

count() / average() / min() / max()


You can count the number of elements in the stream
with count():
List<Integer> nums = [Link](1, 2, 3, 4, 5);
long numberOfElements = [Link]().count();
Find a min or max value among elements
using min() and max() that accept a Comparator. Both methods
return an Optional because a result may not exist. As a result,
you can receive an Optional or throw an exception:
List<Integer> nums = [Link](1, 2, 3, 4, 5);
int minNumber = [Link]()

.min([Link](Integer::intValue))

.orElseThrow(NoSuchElementException::new);

Optional<Integer> maxNumber = [Link]()

.max([Link](Integer::intValue));
You can also count the average of elements with
the average() method:
int[] numbers = {1, 2, 3, 4, 5, 6};
OptionalDouble average =
[Link](numbers)
.average();

findFirst() / findAny()
With the findFirst() method, you can get the first element of
the stream. The method returns an Optional:
String firstProduct = [Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.findFirst()
.orElse(null);
The findAny() method returns any element of the stream. Be
careful with this one: the method returns a random element
regardless of its position in the stream, and there’s no guarantee
that the same element will be chosen every time you invoke this
method.
String anyProduct = [Link]()
.flatMap(order -> [Link]()
.stream())
.map(Product::name)
.findAny()
.orElse(null);
allMatch() / anyMatch() / noneMatch()
The allMatch(), anyMatch(), and noneMatch() methods help
you determine whether the elements meet the provided condition
and return a boolean. The allMatch() method determines
whether all elements satisfy the condition, anyMatch() —
whether any one element satisfies the condition,
and noneMatch() — whether no elements satisfy the condition. In
all cases, the element processing stops as soon as the answer is
determined:
boolean allOrdersMatch = [Link]()
.allMatch(order ->
[Link]().size() > 1);

boolean anyOrderMatches = [Link]()


.anyMatch(order ->
[Link]() > 300);

boolean noOrderMatches = [Link]()


.noneMatch(order ->
[Link]() > 1000);

Dealing with null in


streams
When working with streams, we can come across a
NullPointerException if we try to perform actions on a Collection
with a null value or on elements of a stream with a null value.
Luckily, we can make our code NPE-proof in both situations.

To prevent processing a null stream, we can take advantage of


the [Link]() method, which creates an Optional
of a provided collection. In case the collection is null, it creates an
empty stream:
List<String> nullList = null;
[Link](nullList)
.stream()
.forEach([Link]::println);
What if some objects in a stream are null? In this case, we can
simply filter them out with filter():
List<Product> listWithNulls =
[Link](pencil, null, table);
[Link]()
.filter(Objects::nonNull)
.map(Product::name)
.forEach([Link]::println);

Conclusion
To summarize:

• Stream API enables the developers to process a


collection of elements with functional programming tools.
• Streams describe operations that need to be performed
on the elements of a data source without modifying the
data source.
• There are intermediate and terminal operations.
Intermediate operations won’t be executed until a
terminal operation is invoked.
• There are multiple default intermediate and terminal
operations, but you can also create a custom
intermediate operation with Stream Gatherers.

También podría gustarte