Java es hoy en día el lenguaje de programación más popular y a un que existe detractores, la verdad es que sigue teniendo una gran popularidad, que a decir verdad, yo sigo siendo unos de sus Fans.
Pocas son las versiones de Java que han marcado un antes y un después en el lenguaje, por ejemplo, la introducción de anotaciones y genéricos de la Java 5, o los Lambda expresions y paralelismo de Java 8 y finalmente la llegada de Java 9 trae algunas mejores importantes que son dignas de elogiar.
Al momento de la redacción de este artículo, la versión de Java 9 se encuentra en Beta, pero se espera su liberación el día 21 de septiembre, por lo que, al momento de leer esta nota, es muy probable ya que esta liberada la versión final.
Como sea, la verdad es que cada versión de Java trae una gran expectativa, y por lo general cada versión trae tantos cambios como para escribir un libro completo, por lo que en este artículo trataremos de hablar de lo más relevante y que como programadores podría ser de los más interesante, así que, sin más preámbulos, veamos las mejoras.
Sistema de módulos
Sin lugar a duda, el sistema de módulos es la característica más relevantes y esperadas, pues desde su nacimiento, el JDK de Java ha sido monolítico, lo que implica que tenías todos o nada. En este sentido, las aplicaciones Java requerían montar en el classpath todas las clases del JDK + las clases de los proyectos. lo que implicaba varios problemas, como el espacio requerido para cargar todas las clases al classpath o la colisión en los nombres de las clases de diferentes paquetes.
El sistema de módulos ayuda a que se realice un encapsulamiento real, pues permite definir que paquetes de un módulo se expone a los demás evitando que otros módulos utilicen paquetes que no fueron expuestos, incluso mediante Reflexión.
Para crear un módulo en Java 9, es requerido que el proyecto tengo un archivo llamado module-info.java en la raíz del proyecto, con la siguiente estructura.
El cual deberá declarar mediante la sentencia export los paquetes que podrán ser accedidos por otros módulos, y la sentencia requires para importar los módulos de los que depende. Por default, todos los paquetes que no sean expuestos serán privados de forma predeterminada.
En la imagen podemos observar que el Modulo A requiere del MóduloB, y que el Modulo B expone el paquete com.centripio.moduleb, aunque por otro lado, vemos que el ModuloA importa al módulo completo, no sus paquetes.
Servicios
Otra de las cosas que ofrece la modularización, es exponer servicios. Los servicios son clases que son expuestas mediante una interface, que la implementación real queda totalmente oculta, pues no es necesario exponer mediante export la implementación.
Veamos el siguiente ejemplo, tenemos un Módulo A y un Módulo C, el modulo A requiere el servicio expuesto por el Modulo C, para esto, el Modulo A no debe de conocer la implementación del Módulo C. Para esto tendremos un tercer módulo llamado Hello, el cual solo expone una interface, esta interface será común entre el modulo A y C, el modulo C va exponer el servicio por medio de la interface del Módulo C y el Módulo A buscará todos los servicios expuestos mediante la interface del Módulo Hello.
El archivo module-info del módulo Hello queda de la siguiente manera:
Podemos observar que el módulo Hello expone el paquete com.centripio.hello, el cual contiene la interface IHelloWorld. Al contar con la instrucción exports, le indicamos que todas lo que este en este paquete podrá ser visible por otros módulos.
El archivo module-info del módulo c queda de la siguiente manera:
El Módulo C indica que requiere (requires) del módulo Hello, como podemos ver, no hacemos referencia a los paquetes expuestos, sino a todo el modulo. Por otra parte, le estamos indicando que el módulo expone un servicio (provides) el cual se expone encapsulado mediante la interface com.centripio.hello.IHelloWorld del Módulo Hello, pero la implementación real es la clase com.centripio.modulec.impl.HelloWorldCImpl la cual está dentro del Módulo c. notemos que en ningún momento estamos exponiendo mediante export el paquete de la clase de implementación.
El archivo module-info del módulo A queda de la siguiente manera:
Finalmente, vemos que el Modulo A indica que requiere del Módulo Hello, y adicional, hace uso (uses) de los servicios expuestos por la interface com.centripio.hello.IHelloWorld.
Ahora bien, veamos cómo se ejecuta esto dentro del método Main.
En la línea 8 utilizamos la case ServiceLoader, la cual tiene el método load que recibe una clase/interface para buscar todos los servicios encapsulados por esta interface. El retorno es una lista, pues varios módulos pueden exponer una implementación diferente. Finalmente, iteramos la lista y llamamos el método hello definido en la interface del Módulo Hello. Si has utilizando OSGI, este te podrá resultar algo muy familiar.
Modularize JDK (Jigsaw)
Unos de los principales problemas de Java desde el inicio, ha sido la popularización del JDK, pues cualquier aplicación desarrollada en Java, requiere de una instalación completa del JRE, un que solo requiera unas cuantas API. En Java 9 se soluciona este problema con el proyecto llamado Jigsaw, el cual se viene consolidado desde le Java 8 y que en el Java 9 ya lo podemos ver consolidado.
La idea central de modularizar el JDK consiste en que una aplicación puede ser ejecutada en una versión reducida del JRE, de esta forma, podemos ahorra recursos importantes en nuestros dispositivos, lo cual viene bastante bien la arquitectura de micro servicios o despóticos de bajos recursos.
La imagen anterior ilustra la forma en que trabajábamos con versiones anteriores del JDK, en la cual era necesario tener todas las API´s para que nuestra aplicación funcionara.
Por otra parte, Java 9 nos permite ejecutar nuestra aplicación solo con los módulos necesario.
Métodos privados en interfaces
Una de las novedades que introdujo el Java 8 es la implementación default de las interfaces, sin embargo, todos los métodos tenían que ser públicos. Con la llegada de Java 9, esto cambia, y es posible definir métodos privados, por lo que solo podrán ser utilizados en dentro de la misma interface, mas precisamente, solo se podrán llamar dentro de los métodos por default. Ya que fuera de la interface no será accesibles, incluso en las clases que las implementen.
Se pueden declarar métodos privados y métodos privados estáticos.
Como podemos ver en este ejemplo, solo es necesario declararlos con el modificador de acceso private, y no es necesario utilizar la instrucción default.
Anotación @deprecated mejorada
Desde su creación, la anotación @deprecated ha servido a los programadores para enterarse si una clase o método ya esta no está aconsejada para su uso, debido a que ya no recibirá más soporte o por que se ha creado una nueva funcionalidad mejor que sustituye a la anterior, sin embargo, no era posible determinar desde cuando fue deprecada, y si ha sido marcada para ser eliminada en el futuro. El nuevo formato de la anotación es la siguiente:
El atributo forRemoval indica si ha sido marcado para ser removido permanentemente en las siguientes versiones y since le indica al programador desde que versión ha sido marcado como deprecated.
Métodos de fábrica de conveniencia para colecciones
Java 9 a incluido algunos métodos de utilidad que ayudan a crear colecciones inmutables de una forma mucho más simples, pues eliminan la verbosidad requerida en versiones para crear e inicializar las colecciones inmutables.
Las colecciones inmutables son aquellas en las que no es posible agregar o eliminar valores, y son muy utilizadas en procesos concurrentes, pues no generan bloqueos.
Aquí algunos ejemplos de cómo crear una List, Set y un Map:
Como podemos observar, utilizamos el método of de la interface de la colección deseada, y como resultado, obtendremos una implementación de colección, pero inmutable. En el caso de las listas y Sets, solo mandamos una lista de valores, que será agregados en ese orden, pero en el Map, los valores vienen en pares, el primero es el Key y el segundo él es value.
Existen algunas reglas básicas que debemos de entender cuando utilizamos estas colecciones inmutables.
- Si intentamos agregar o eliminar un valor de la colección, lanzarán UnsupportedOperationException
- En las List y Set no se permiten valores null y en el Map, la llave no puede contener valor null, ya que, en cualquiera de estos dos casos, se lanzará NullPointerException.
- Instancias basadas en valores, esto quiere decir que el método of puede devolver la misma instancia para una lista con los mismos valores, por lo que: List.of(“foo”, “bar”) == List.of(“foo”, “bar”) podrían devolver la misma instancia, todo dependerá de la JVM
- La colección resultante será serializable si los valores lo son, en caso contrario, la lista no será serializable.
Mejoras en el try-with-resources
Una de las novedades que nos trajo Java 7 fue la posibilidad de administrar recursos dentro de un Try-Catch y que este los cerrar por nosotros al terminar el bloque, sin embargo, tenía el principal inconveniente de que requería una nueva variable, ya sea declararla y asignarle el valore de un recurso existente o declarar la variable y abrir en ese momento el recurso:
El ejemplo anterior es como se hacía en versiones pasadas, la nueva forma de hacerlo es la siguiente:
En Java 9 es posible utilizar una variable existen, con la única condición es que la variable input sea efectivamente final, es decir, que se asigne un valor en su declaración y nunca se modifique.
Nueva API HTTP
La llegada de Java 9 nos trae una nueva API para establecer conexiones HTTP de una manera más ordena y con soporte para HTTP 2.0, y aunque todavía es un API que están en la incubadora, ya podemos apreciar algunas de sus características.
El soporte para HTTP 2.0 es sin duda lo más relevante del API, porque mostraremos un ejemplo simple de cómo utilizarlo. No quiero entrar en detalles acerca de las mejoras de HTTP 2.0 pues en el tema central de este artículo.
Lo primero que debemos de saber es que esta nueva API no viene como parte del API Base de Java, por lo que debemos de importarlo como módulo:
El nuevo API de HTTP divide su funcionalidad en 3 principales clases:
- HttpClient: gestiona la creación y el envío de las solicitudes
- HttpRequest: Se utiliza para construir el request para ser enviado por medio de HttpClient
- HttpResponse: Contiene la respuesta de la petición enviada por HttpClient.
Ahora veamos un pequeño ejemplo de cómo consumir un recurso WEB como es la página de Google:
En este ejemplo rustico, pero bastante claro, podemos apreciar como utilizamos el nuevo API para consumir la página de Google. En la línea 10 creamos un cliente HTTP y en la línea 11 imprimimos la versión del cliente, el cual deberá arrojar “Http version: HTTP_2”. En la línea 14 y 15 creamos un objeto HttpRequest para consultar la página de google mediante el método GET. En la línea 19 enviamos el Request mediante la clase HttpClient y obtenemos un objeto HttpResponse como respuesta.
Hay varias cosas que no podemos abarcar en este artículo, pero cabe mencionar que el API de HTTP soporta conexiones síncronas y asíncronas además que permite crear clientes de WebSockets.
TIFF Image I/O
El formato TIFF ha venido tomando fuerza, pues es el formato predeterminado para el sistema operativo IOS. Pero a pesar de tener mucha fuerza, la realidad es que hasta la versión 9 de Java se empaqueta como una solución definitiva.
En las versiones anteriores de Java proporcionaba un conjunto de codecs para mostrar los formatos de imágenes más populares, como JPG, PNG, GIF, etc. En versiones de pasadas de Java, era necesario utilizar el paquete com.sun.media.imageio.plugins.tiff el cual en la versión 9 de Java, es reempaquetada en javax.imageio.plugins.tiff.
Multi-Resolution Images
Con la llegada de Smartphones, tablets y pantallas de diferentes resoluciones, se ha complicado la forma en que mostramos las imágenes en el dispositivo, pues en muchas ocasiones no vemos obligados a redimensionar las imágenes o crear condiciones que muestren una imagen u otra, según la resolución de pantalla. Cabe resaltar que esta nueva funcionalidad no se encarga de redimensionar una imagen, sino que, dado una serie de imágenes, nos ayuda a seleccionar la que mejor se ve para una resolución dada.
Con la llegada de Java 9, se agregan una serie de clases de utilidad que nos ayudarán a seleccionar la imagen más adecuada para una resolución de pantalla. Las clases son las siguientes:
- MultiResolutionImage: Interface base para todas las imágenes multi resolución
- AbstractMultiResolutionImage: Clase abstracta que implementa la funcionalidad base
- BaseMultiResolutionImage: Clase concreta que implementa la funcionalidad mínima.
No quiero entrar un mucho detalle acerca de las clases, pues nos alargaríamos demasiado, por lo que me quiero centrar en un ejemplo simple. Imaginemos que tenemos que mostrar el avatar de nuestro usuario o un banner publicitario, y según la resolución de pantalla, podemos mostrar una imagen que se vea mejor. Para este ejemplo, vamos realizar una prueba con una imagen de 64×64, otra de 128×128 y otra de 256×256. Finalmente le solicitaremos a la interface MultiResolutionImage que nos de la imagen que mejor se adecua a la resolución solicitada
En la línea 18 definimos las URL de las 3 imágenes con diferentes resoluciones. En la línea 25 leemos las imágenes para crear un objeto Image. En la línea 27 creamos el objeto MultiResolutionImage a partir de las 3 imágenes con diferente resolución. En las líneas 31,32 y 33 solicitamos 3 imágenes, para resoluciones 64, 128 y 256, por lo que la interface MultiResolutionImage nos deberá retornar la imagen que mejor se ajuste. Cabe mencionar que las resoluciones no tienen que ser exactas con las imágenes. Ya que, en este caso, MultiResolutionImage nos arrojara la resolución que mejor se adapta a la resolución. Veamos cómo se ejecuta este ejemplo:
Mejoras al API de proceso
Esta es una mejora que ya muchos están esperando, pues al ejecutar un proceso con java.lang.Process era muy difícil tener un control real de los procesos del sistema operativo, por ejemplo, obtener el Process ID o PID, realizar una acción cuando el proceso terminara u obtener los procesos hijos, entre otras cosas. Para esto, Java 9 ofrece la interface ProcessHandle y su clase internar ProcessHandle.Info la cual nos da información acerca del proceso.
Por ejemplo, veamos como iniciar el block de notas y obtener el PID
Esto solo es una pequeña parte de todo el potencial de esta mejora, para ver más información puedes ver la siguiente liga: https://www.javaworld.com/article/3176874/java-language/java-9s-other-new-enhancements-part-3.html
Plattform logging API and Service
Una de las grandes problemáticas al modulizar el JDK era que el Módulo Base (clases base) no podía depender del Módulo (java.loggin) pues crearía una referencia circular, lo cual todos sabemos que es una mala práctica, por ello, se decidió crear un API de loggin integrado al Módulo base (java.lang.Loggin) el cual sirve primero que nada para desacoplar la dependencia con java.loggin pero también se utiliza para crear Servicios por módulo.
Otra de las ventajas que ofrece este nuevo API, es que permite registrar mensajes en el log antes de que todos los módulos estén cargados.
Para ver un ejemplo más concreto, te recomendamos la siguiente liga: http://javasampleapproach.com/java/java-9/java-9-platform-logging-and-service
Deprecated Applet API
Debido al declive de los Applets y que los navegadores cada vez soportan menos las tecnologías que se ejecutan con plugin como Java o Flash, Oracle ha decidido marcar todo el API de Applets como Deprecated, y un que siguieran siendo parte del JDK, se advierte que será eliminado permanentemente del JDK en versiones posteriores, un que todavía no confirman en cual.
JavaDB eliminado del JDK
A partir de la versión Java 9, la base de datos JavaDB o más bien Apache Derby, será eliminada definitivamente del JDK, por lo que se podrá descargar por separado. En las versiones pasadas, se podía encontrar en la carpeta db del JDK.
VisualVM removido
La herramienta VisualVM que es utilizada analizar la JVM en tiempo de ejecución fue agregada como parte del JDK en la versión 6 del JDK, pero a partir de la versión 9 de Java, es removida del JDK y se tendrá que descargar por separado. El proyecto VisualVM es mantenido por la comunidad de código abierto y puede ser descargado en: https://visualvm.github.io/
Conclusiones
En este artículo solo quise hablar de las cosas más sobresaliente. Pero sin lugar a duda, el sistema de módulos es las características más sobre saliente de esta versión, y un qué nivel lenguaje no tuvimos mejoras, la realidad es te la modularización era unos de los grandes problemas que tenía Java, y más aun con la llegada de la arquitectura de microservicios y nuevos lenguajes como NodeJS que son modulares desde su nacimiento. Si quieres profundizar más acerca de las novedades que nos trae Java 9, puede ver la siguiente url: http://docs.oracle.com/javase/9/whatsnew/toc.htm