Novedades de Java 9, más allá de la modularidad

Escrito por el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

La modularidad introducida en Java 9 no es la única novedad incorporada en esta nueva versión. Aunque haya llegado a eclipsar al resto hay otra buena colección de mejoras que sin duda facilitarán la programación con uno de los lenguajes más empleados por los programadores aún después de más de 20 años.

Java

La última versión mayor de Java fue publicada en el año 2014, hace ya mucho tiempo para la época actual donde las cosas avanzan a un ritmo muy rápido. Java 8 introdujo en el lenguaje notables cambios como las expresiones lambda dotándolo de capacidades funcionales y mayor expresividad en menos líneas de código junto con referencias a métodos e interfaces funcionales que permite crear implementaciones anónimas de interfaces con una lambda, se añaden los streams como nueva forma de iterar sobre las colecciones, interfaces con métodos por defecto o estáticos que aumentan la compatibilidad hacia atrás que siempre se le ha dado gran importancia en la plataforma o una nueva API para fechas que solventa las deficiencias de la anterior. Grandes cambios en el lenguaje tan importantes como los que supusieron Java 5.

Después de algunos aplazamientos principalmente por implementar la modularización de la forma correcta sin que en un futuro suponga un problema ha sido publicada en septiembre de 2017 la versión de Java 9. La característica más llamativa es la modularización de la plataforma con Java 9 que supone grandes mejoras como una mejor encapsulación de los paquetes, interfaces entre módulos bien definidas y dependencias explícitas que proporcionan optimización al usarse sólo los módulos que se necesitan, mayor seguridad al ser menor la superficie de ataque y configuración confiable al comprobar las dependencias al compilar o iniciarse la máquina virtual.

 Introducción

Pero Java 9 además de los módulos incorpora en la plataforma otros cambios destacables. Esta no es una lista exhaustiva pero si contiene muchas de ellas.

Tabla de novedades de Java 9

Tabla de novedades de Java 9

Duke Java 9

Nuevas características

Métodos factoría para colecciones

Aún Java no incorpora en el lenguaje una forma de definir como literales elementos tan comunes como listas, conjuntos o mapas. Como alternativa se proporcionan métodos factoría estáticos para crear este tipo de estructuras de datos usando métodos por defecto en sus respectivas interfaces. Además, estos métodos crean colecciones inmutables.

Aparte de definir este tipo de colecciones de una forma mucho más sencilla que hasta Java 8, las colecciones además son significativamente más eficientes. En el caso de Java 8 un Set con dos elementos de capacidad 3 requiere un objeto wrapper para la colección inmodificable, 1 HashSet, 1 HashMap, 1 Object[] de longitud 3, 2 dos nodos uno para cada elemento requiriendo en total unos 152 bytes. En el caso de Set creado con Java 9 requiere solo 20 bytes que comparados con los 152 es una mejora significativa más al tener en cuenta que este tipo de estructuras de datos son utilizadas de forma numerosa en cualquier programa. Aún estando hablando de bytes multiplicado por cada uso en algunos casos la reducción de memoria puede ser apreciable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Java 8
List<String> list = Collections.unmodifiableList(Arrays.asList("a", "b", "c"));

Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map = Collections.unmodifiableMap(map);

// Java 9
List<String> list = List.of("a", "b", "c");
 
Set<String> set = Set.of("a", "b", "c");
 
Map<String, Integer> map = Map.of("a", 1);
Collections.java

Mejoras en la clase Optional

Los métodos or() y ifPresentOrElse() así como stream() mejoran la experiencia de uso en esta clase que contiene o no un objeto. El método or() en caso de no contener el Optional un objeto permite proporcionar un Optional alternativo. Los métodos ifPresent() y ifPresentOrElse() permiten realizar una acción con el objeto del opcional si está presente u otra acción con un valor vacío si no está presente. El método stream() convierte el Optional en un stream de cero o un elemento.

Mejoras en la API de streams

Los nuevos métodos de los streams dropWhile(), takeWhile() permiten descartar o tomar elementos del stream mientras se comprueba una condición. El método ofNullable() devuelve un stream de un elemento o vacío dependiendo de si el objeto es null o no. Los métodos iterate() permiten generar un secuencia de valores similar a un bucle for.

También se añaden varias interfaces para el uso de reactive streams.

REPL con jshell

Otra de las características destacables es la incorporación de la herramienta JShell para evaluar código siguiendo el patrón Read-Evaluate-Print-Loop o REPL para hacer pruebas de código sin la necesidad de un IDE, una herramienta de construcción o toda la infraestructura de un proyecto. Esta herramienta es el comando jshell.

Se pueden introducir expresiones que son evaluadas y comandos precedidos por el caracter /. Soporta asistencia de código con la tecla tabulador.

jshell

jshell

Java 8 proporciona varios runtimes reducidos con los compact profiles que contienen algunas clases menos de las que incluye el JDK completo. Sin embargo, estos runtimes están preconfigurados y las aplicaciones deben optar por el que ofrezca todas las clases que necesita.

En Java 9 se proporciona jlink que un sustituto más capaz que los compact profiles. Permite generar runtimes aprovechando la nueva modularidad del JDK con únicamente los módulos que necesite la aplicación.

Esto es especialmente útil para los contenedores de Docker y los entornos cloud ya que permite generar imágenes de contenedores con un tamaño significativamente menor. Por ejemplo, una imagen de Docker basada en la distribución Alpine Linux con el JDK completo ocupa unos 360 MiB, con jlink si una aplicación solo necesita del módulo java.base se puede generar un runtime con únicamente ese módulo, con este runtime adaptado la imagen del contenedor tiene un tamaño mucho menor, en este caso de únicamente de unos 40 MiB.

Concurrencia

Se añade un framework con un conjunto de clases para programación reactiva de publicación-subscripción con las clases Flow, Flow.Processor, Flow.Subscriber, Flow.Publisher y Flow.Subscription. La clase Subsription posee dos métodos: cancel() y request() para dejar de recibir mensajes y solicitar recibir n mensajes en la siguientes llamadas de onNext​().

El método copy() de la clase CompletableFuture permite obtener un copia completándose con el mismo valor cuando la operación se completa normalmente.

Variable Handles

Una de las justificaciones de la modularidad es el uso que hasta ahora se le ha dado a la famosa clase interna del JDK sun.misc.Unsafe. Para proporcionar parte de la funcionalidad de esta clase en una API pública se introduce la clase VarHandle para referenciar a variables estáticas y no estáticas así como a arrays. Estas instancias se obtienen mediante la clase MethodHandle.Lookup. Una vez obtenida una instancia de VarHandle se pueden realizar operaciones de bajo nivel sobre la variable que referencia como operaciones atómicas comparar y establecer pero sin la sobrecarga de rendimiento con las clases equivalentes del paquete java.util.concurrent.atomic.

Para la mayoría de los programadores esto no les afectará directamente pero sí será una mejora para los desarrolladores de librerías importantes muy populares que si usan la clase Unsafe y deberían aprovechar estas nuevas capacidades para dejar de usar la clase interna del JDK en la medida de lo posible.

Actualizaciones en la API para procesos

Ahora es posible obtener el identificador del proceso o pid con el método pid() y los procesos hijos y descendientes con los métodos children​() y descendants​() respectivamente.

StackWalker

La clase StackWalker permite obtener un stream secuencial de StackWalker.StackFrames del thread actual para procesar la pila de llamadas o stacktrace.

1
2
StackWalker.getInstance().walk(s -> s.limit(5).collect(Collectors.toList()));

StackWalker.java

Strings compactos

Internamente los la clase String contiene un array de char, cada char se representa en formato con la codificación UTF-8 ocupando 16 bits o 2 bytes por cada caracter. Para cadenas en aquellos lenguajes como inglés los caracteres pueden ser representados usando un único byte.

Una buena parte de la memoria ocupada en la JVM por cualquier aplicación es debido a las cadenas de modo que tiene sentido compactar aquellas cadenas en las que sea posible representándolas con un único byte por caracter.

Lo mejor de todo es que esta optimización será transparente para los programadores y para las aplicaciones proporcionando una reducción en el uso de la memoria y aumento del rendimiento, también en el recolector de basura.

Recolector de basura G1 por defecto

Se cambia el recolector de basura por defecto al llamado G1 optimizado para una balance adecuado entre alto rendimiento y baja latencia. En los siguientes artículos se explica de forma más detallada. Al igual que los string compactos para la mayoría de los programadores será un cambio transparente que no tenga repercusión en la forma de programar las aplicaciones.

Identificador para variables _

El identificador _ queda reservado para en un futuro usarlo en parámetros a los que no se les dé un uso y no sean relevantes como por ejemplo en las lambdas. También se evaluará en el proyecto Amber usar este identificador para tipos diamante parciales como Foo<String, _> o en referencias a métodos foo.<String, _>bar().

Métodos privados en interfaces

Ahora se pueden crear métodos privados en interfaces como utilidad a las implementaciones de los métodos por defecto.

Mejor try-with-resource

Ahora las variables finales o efectivamente finales pueden ser colocadas en los bloques try-with-resource simplificando algunos usos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// A final resource
final Resource resource1 = new Resource("resource1");
// An effectively final resource
Resource resource2 = new Resource("resource2");

// Java 7, 8
try (Resource r1 = resource1;
     Resource r2 = resource2) {
    ...
}

// New and improved try-with-resources statement in Java SE 9
try (resource1;
     resource2) {
    ...
}
TryWithResources.java

Javadoc

Ahora la documentación Javadoc se genera con marcado de HTML 5 e incluye un cuadro de búsqueda para encontrar más fácilmente tipos y métodos.

Javadoc

Javadoc

Archivos Jar multiversión

Los desarrolladores de librerías para dar soporte a varias versiones de Java debían optar entre generar un artefacto para cada versión o un único archivo jar limitándose a usar la mínima versión soportada y sin aprovechar las nuevas capacidades de siguientes versiones. Esto es un impedimento para el uso de nuevas versiones.

Con Java 9 se puede generar un único archivo jar con algunas clases para una o varias versiones de Java. Por ejemplo, en un archivo jar con las clases A, B, C y D compatibles con Java 6 el desarrollador ahora puede decidir que para la versión 9 la clase A y B sean unas optimizadas para esta versión. Esto se consigue con una estructura específica de directorios en el archivo jar, ubicándose la clase optimizada para Java 9 A en META-INF/versions/9/A.class y para Java 10 en META-INF/versions/10/A.class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
jar root
  - A.class
  - B.class
  - C.class
  - D.class
  - META-INF
     - versions
        - 9
           - A.class
           - B.class
        - 10
           - A.class
Multirelease.out

Nuevo modelo de publicación

A partir de la publicación de Java 9 se cambia el modelo de publicación de nuevas versiones optando por una basada en calendario en vez de una por características a incluir. El caso de versiones que han de incluir las características previstas ocasiona el problema que si una se retrasa provoca un retraso en la versión. Con el modelo basado en fechas fijas preestablecidas la versión se liberará con aquellas características que estén listas en la fecha planificada de publicación sin ser retrasadas por aquellas que no.

Se ha optado por producir una nueva versión cada seis meses con el OpenJDK y licencia GPL para satisfacer las necesidades de los desarrolladores y una versión con soporte de largo plazo cada tres años para satisfacer las necesidades de tiempo de soporte prolongado de las empresas, Oracle JDK.

Para finalizar un par de libros, Java 9 Revealed y Java 9 Modularity Revealed que explican detalladamente las novedades de la modularidad, las novedades incluídas en este artículo y algunas otras más.

Y unos buenos vídeos sobre las nuevas características de Java 9, uno en español y otro en inglés.


Comparte el artículo: