-
-
-
-
URL copied!
Artículo desarrollado por Alejandro Perez.
Java 8 nos acompaña desde Marzo de 2014, y ya va siendo hora de que vayamos despidiendola para dar a lugar a las nuevas características de las recientes versiones Java 13 y Java 14.
¿Y las versiones entre medio? Bien, también enriquecieron al lenguaje y a la plataforma, pero ninguna fue tan disruptiva como lo fue Java 8.
En este artículo haremos un breve repaso de por qué esta versión es considerada un punto de quiebre en lo que es el lenguaje, y la forma de pensar el código al escribir en Java.
Java decide apostar fuerte
Situándonos en el 2014, el estado del arte nos presentaban muchas opciones al momento de elegir un lenguaje de programación con el cual desarrollar.
Los cambios más notables de este año fueron un incremento del 300% en la popularidad de Objective-C, un aumento del 100% en C#, así como un aumento del 33% de Javascript; mientras PHP perdió un 55%, Perl cayó un 16% y Java se redujo un 14%. https://web.archive.org/web/20140224142555/http://blog.codeeval.com/2014
En este contexto, los arquitectos responsables se dieron cuenta que el rumbo se encaminaba a la adopción de características de programación funcional, y que los desarrolladores (y con ellos la industria) estaba demandando cambios en la forma de entender el lenguaje.
Para dar el puntapié inicial, decidieron incluir las siguientes (en ese entonces nuevas) características a Java:
- Clase Optional
- Funciones Lambda
- Streams
Clase Optional
Todo objeto en Java puede contener un valor o no, ciertamente esto no es una novedad. Antes de Java 8, no teníamos una "entidad" que nos represente este comportamiento por lo que por defecto tenemos el valor especial null para denotar la ausencia de un valor dentro de un objeto.
Esta posibilidad de que un objeto sea potencialmente null nos llevaba (y nos sigue llevando) a caer en la famosa y odiada excepción NullPointerException.
Para evitar esta situación, Java 8 agrega la nueva clase Optional que no es otra cosa que un contenedor diseñado para contener un valor o encontrarse "vacío". Este enfoque puede parecer simple, pero nos facilita de sobremanera el chequeo de valores nulos en nuestro código.
// Without optionals
Computer myCommodore = retroStore.getCommodore();
if (myCommodore != null) {
System.out.println(myCommodore)
} else {
System.out.println("Sorry, Commodore machines are out of stock!")
}
// With optionals
Computer myCommodore = retroStore.getCommodore();
Optional<Computer> optionalComputer = Optional.ofNullable(myCommodore);
if (optionalComputer.isPresent()) {
System.out.println(optionalComputer.get())
} else {
System.out.println("Sorry, Commodore machines are out of stock!")
}
Funciones Lambda
Las funciones Lambda no eran algo nuevo al momento de la creación de Java 8, pero esta fue la primera versión que las incorpora al lenguaje con nuevas estructuras y sintaxis.
Básicamente, una función Lambda es una función anónima que no necesita una firma formal o definida dentro de una clase. La creamos al vuelo, sujeta al contexto donde es creada.
Para crear funciones Lambda, Java 8 incorpora al lenguaje el operador "flecha" (->) que se usa así:
(PARAMS) -> { FUNCTION_BODY }
Donde:
- Podemos tener 0, 1 o 2 parámetros. Si usamos 0 o 2 parámetros debemos usar paréntesis, si usamos uno solo los paréntesis son opcionales.
- En el cuerpo de la función, podemos omitir el uso de llaves en caso de tener solo una línea, en caso contrario debe llevar llaves y la palabra clave return si es que necesitamos retornar un valor.
Otra forma de "pseudo" Lambda es la invocación de métodos directos, por medio del operador "::". Esto genera una referencia al método, que en algunos casos puede directamente sustituir una función lambda en operaciones simples.
ReferenceClass::ReferredMethod
Dependiendo del origen del método que necesitamos usar, podemos clasificar estas referencias en tres categorías:
● Acceso a métodos estáticos
(String msg) -> System.out.println(msg)
// is the same that
System.out::println
● Acceso a métodos de instancia
(Catalog catalog, int itemKey) ->
catalog.getProduct(itemKey) // is the same that
Catalog::getProduct
● Acceso a métodos de instancia de un objeto existente
Catalog catalog -> catalog.getBestProduct()
// is the same that
this::getBestProduct
Streams
Sin el agregado de este concepto al lenguaje, no hubiéramos podido aprovechar todo el potencial que nos ofrece el uso de los objetos Optional y las funciones Lambda.
Básicamente un Stream es una secuencia de elementos que soporta operaciones secuenciales y en paralelo. Un Stream no almacena datos, trabaja sobre la estructura de datos origen (Collections, arreglos por ejemplo) y produce datos canalizados que pueden ser procesados luego.
Comparemos una iteración clásica sobre una Collection, sin uso de Streams:
private static int sumIterator(List<Integer> list) {
Iterator<Integer> it = list.iterator();
int sum = 0;
while (it.hasNext()) {
int num = it.next();
if (num % 10 == 0) {
sum += num;
}
}
return sum;
}
Tenemos un enfoque clásico que crea un Iterator desde nuestra lista y trabaja con ella mientras exista un elemento por procesar en el Iterator. (método hasNext())
- Como desarrolladores, solo queremos obtener la suma de los números, pero en este caso necesitamos proveer detalles de cómo iterar los elementos. Esto recibe el nombre de "iteración externa" porque el desarrollador es quien controla el algoritmo de iteración. Esto incluye el uso de estructuras como for, foreach, while por ejemplo.
- El algoritmo es secuencial por naturaleza, no tenemos una forma fácil de paralelizar.
- Es bastante código en contraste con lo que queremos hacer, ¿no?
Los arquitectos de Oracle tomaron nota de estas situaciones, he incluyeron en la API de los Streams métodos con la habilidad de iterar a través de los elementos, sin la necesidad de proveer detalles de cómo debería hacerse esa iteración. Además, la API provee nuevas formas de acceso a los elementos.
Podemos reescribir el ejemplo anterior de la siguiente forma:
private static int sumStream(List<Integer> list) {
return list.stream()
.filter(i -> (i % 10 == 0))
.mapToInt(i -> i)
.sum();
}
Donde:
- stream(): Crea un Stream a partir de una lista de enteros.
- Stream<T> filter( Predicate<T> p ): Retorna un Stream con elementos que aplican al predicado "p".
- IntStream mapToInt( function_lambda ):Toma uno a uno los elementos del Stream y los procesa con la funcion Lambda. Este retorna un Stream con enteros de la clase IntStream.
- sum(): Suma cada elemento del Stream y retorna un resultado.
Se ve mucho más simple, ¿verdad? No solo eso, los Streams son una vía con la cual iterar elementos que ya están alojados en memoria, sin un consumo extra de memoria notable en contraste con la cantidad de elementos que iteramos.
Esto le permite generar cantidades infinitas de elementos, cuya creación puede ser controlada por el desarrollador. También nos permite procesar archivos de gran tamaño con un impacto mínimo, permitiéndonos centrarnos en la lógica a implementar y no en la iteración de los datos.
Conclusiones
Los Streams y su API fueron diseñados con el concepto de funciones Lambda y Optional en mente desde el inicio. Estos a su vez fueron pensados para cubrir una necesidad creciente de herramientas que nos ofrecen aspectos de programación funcional y programación dinámica.
Java 8 ha sido un punto de quiebre que ha evolucionado el lenguaje, permitiendo crear muchas técnicas de programación avanzadas que fueron incluidas posteriormente. Con el conocimiento de estas características podemos entender por qué son importantes, y cómo nos ha ayudado a codear de una manera más eficiente y clara.
Este artículo ha tomado solo una pequeña parte de los cambios introducidos por Java 8, ya que esta versión ha marcado un antes y un después no solo para el lenguaje, sino para todas las aplicaciones, sistemas y tecnologías basadas en Java.
Gracias por todo Java 8, ya puedes descansar, es hora de seguir adelante. Tu legado sigue vigente y activo al día de hoy, incluso en Java 14.
Links útiles
Para profundizar en estos temas, les recomiendo las siguientes páginas:
- [Oracle] What's New in JDK 8
- [Oracle] Processing Data with Java SE 8 Streams, Part 1
- [Oracle] Part 2: Processing Data with Java SE 8 Streams
- [Oracle] Stream (Java Platform SE 8 )
- [Oracle] Lambda Expressions
- [Oracle] Lambdas and Streams in JDK 8
- [journaldev.com] Java 8 Features with Examples
- [softwaretestinghelp.com] Prominent Java 8 Features With Code Examples
- [Baeldung] https://www.baeldung.com/java-optional
- [Stackify] https://stackify.com/optional-java/
Top Insights
Escribiendo User Stories en Agile
AutomotiveCommunicationsConsumer and RetailFinancial ServicesHealthcareManufacturing and IndustrialMediaTechnologyWhat is TM Forum Frameworx and how to...
UncategorizedAutomotiveCommunicationsConsumer and RetailFinancial ServicesHealthcareManufacturing and IndustrialMediaTechnologyImpact Mapping en Metodologías ágiles
AutomotiveCommunicationsConsumer and RetailFinancial ServicesHealthcareManufacturing and IndustrialMediaTechnologyTop Authors
Blog Categories
Trabajemos juntos
Contenido Relacionado
5 razones por las que tu proyecto necesita un Business Analyst
Contar con un Business Analyst (BA) en tu equipo no solo te ayudará a delegar tareas más operativas, sino que también potenciará al equipo de desarrollo y contribuirá significativamente al éxito de tu proyecto de desarrollo de software.
Conocer más
7 claves para ser un miembro de un equipo efectivo
Un gran desarrollador necesita trabajar tanto en sus habilidades técnicas como en sus habilidades blandas, ya que estas forman la base para cualquier profesional que quiera ser una pieza efectiva e inspirar un cambio positivo en su equipo y organización. He recopilado una serie de recomendaciones que considero básicas y de vital importancia para trabajar … Continue reading ¡Despidamos a Java 8! →
Conocer más
Share this page:
-
-
-
-
URL copied!