Llegó la primavera, y con ella, Java 14.
Marzo de 2020.
Este mes, además de llegar la primavera y el cambio de hora, nos llega una nueva versión de Java, y ya es la 14.
Un momento... ¿Qué está pasando con las versiones de Java?
¿Java 14?, ¿Ya está aquí?
Hace 3 años, Oracle sorprendió a la comunidad con su nueva propuesta de cambio de la política de lanzamiento de nuevas versiones de Java. Y tras este anuncio, las versiones se lanzan en base al tiempo transcurrido y no al conjunto de funcionalidades incluidas en las versiones.
La idea es que una nueva versión LTS (Long Term Support) será liberada cada 3 años, y la cual tendrá un soporte de 8 años de duración.
A su vez, nuevas versiones de Java serán lanzadas cada 6 meses con el objetivo de recoger las últimas funcionalidades incorporadas y facilitar el proceso de migración de una versión a otra, reduciendo el impacto de migración en aquellos proyectos que actualicen la versión de Java tras cada lanzamiento. Estas versiones lite también hacen posible la recolección del feedback de una de las comunidades más activas en el mundo del software.
Como podemos ver en la imagen anterior, y para situarnos en el contexto, Java 11 es la versión LTS más reciente liberada por Oracle (septiembre de 2018). Tras esta, se han ido liberando versiones periódicamente cada 6 meses (Java 12, Java 13…) y seguirán liberándose hasta la próxima LTS, Java 17, que será liberada en septiembre de 2021…
Esta versión está justo a mitad de camino entre las 2 versiones LTS, Java 11 y Java 17, por lo que aunque nuestros entornos solamente puedan presumir de tener la última versión de Java instalada durante 6 meses, es un buen momento para recoger las novedades que llegan con esta versión para que nos ayuden a decidir si es buen momento para migrar y dejar el camino un poquito más llano de cara a alcanzar la próxima LTS, Java 17.
Principales novedades de Java 14
Resumir en un post todas las novedades es complicado, por lo que intentamos recopilar algunas de las más interesantes que presenta esta nueva versión.
Antes de comenzar a analizar los cambios, es importante conocer su clasificación:
- Experimental: Mejoras a nivel de máquina virtual que son consideradas incompletas o inestables. Si quieren ser activadas deben de hacerse a través de flags a la hora de arrancar Java.
- Incubator: Nuevas APIs distribuidas en módulos separados que se encuentran en la paquetería “jdk.incubator.” Estos módulos deben de ser también añadidos explícitamente si se quieren usar.
- Preview Feature: Mejoras que Java añade a la versión que aunque no tienen por qué ser finales, serán incluidas en una versión LTS. Deben de estar desarrolladas al menos al 95%. La idea es recoger feedback de la comunidad para saber si evolucionarla. Pueden existir "Second previews" que son evoluciones de una mejora incluida en versiones anteriores como "Preview".
- Standard Feature: Cambios que han sido añadidos definitivamente en esta versión.
Record (Preview feature)
Intenta reducir el tamaño de las clases planas que tantas veces hay que utilizar como por ejemplo cuando necesitamos deserializar objetos. Pongamos que disponemos de un JSON que representa un empleado:
{
"mimacom_employee": {
"username": "albert.einstein",
"id": 245
}
}
¿A quién no le da pereza tener que crear una clase plana que te permita deserializar este JSON y representarlo en un objeto en JAVA?
Vale que los IDEs de hoy en día ayudan mucho en la creación de este tipo de clases, pero esto hace que tengamos que crear la clase con los atributos, los métodos getters, setters, sobrescribir los métodos equals(), hashcode(), toString()…
public class MimacomEmployee {
private final String userName;
private final long id;
public MimacomEmployee(String userName, long id) {
this.userName = userName;
this.id = id;
}
public String getUserName() {
return userName;
}
public long getId() {
return id;
}
@Override
public int hashCode() {
//...
}
@Override
public boolean equals(Object o) {
//...
}
@Override
public String toString() {
return "MimacomEmployee{" + "userName='" + userName + '\'' + ", id=" + id + '}';
}
}
¿No es un poco molesto tener que crear una clase con unas 40 líneas de código necesarias solo para esto?
¡Bienvenidos a Java Records!
Java 14 crea un nuevo concepto: "record", un nuevo componente al que solo hay que pasarle los atributos como si fuesen parámetros de constructor. Un record heredará automáticamente y sin tener que escribir ni una línea de código: el constructor canónico, setter y getters para cada uno de los atributos, así como los métodos heredados de Object: equals(), hashCode(), y toString().
public record MimacomEmployeeRecord (String userName, long id) { }
Con apenas una línea de código tendríamos la misma funcionalidad ofrecida en la clase plana anterior y podríamos utilizar este record creándolo como si de una clase se tratase, utilizando sus métodos para acceder a sus atributos de la misma manera:
MimacomEmployeeRecord mimacomEmployeeRecord = new MimacomEmployeeRecord ("albert.einstein", 245);
LOGGER.info(mimacomEmployeeRecord.getUserName());
Aunque tienen algunas restricciones, como por ejemplo, que un record no puede extender de ninguna otra clase, no se pueden crear atributos, son constantes, llevan de manera implícita el modificador “final”, por lo que no pueden ser modificados ni pueden ser abstractos… 3 líneas de código siguen siendo mucho más cómodas que las 40 necesarias de una clase.
Patrón matching para ‘instanceof’ (Preview Feature)
Cada vez que hemos utilizado 'instanceOf' para consultar si un objeto es de un tipo determinado y poder utilizarlo más tarde, debíamos de crear una variable de ese tipo y hacer el casting correspondiente.
...
if (obj instaceOf MimacomEmployee) {
MimacomEmployee mimacomEmployee = (MimacomEmployee) obj; //Declaración de variable y casting
}
...
A partir de ahora, y gracias a esta mejora, una variable será creada automáticamente con el valor del objeto sobre el que estamos haciendo el 'instanceof':
...
if (obj instanceof MimacomEmployee mimacomEmployee) {
mimacomEmployee.getId(); // No hay que delcarar una variable ni realizar el casting explícitamente
}
...
Mejoras en NullPointerExceptions (Standard Feature)
El tan temido mensaje producido por un NullPointerException, ha sido mejorado con el objetivo de aportar más información a los desarrolladores sobre cuándo y cómo se ha producido. El mensaje indicará a partir de esta versión qué variable ha producido la excepción. Por ejemplo en el siguiente caso en el que intentamos realizar una operación sobre el userName del objeto MimacomEmployee que es null:
MimacomEmployee mimacomEmployee = new MimacomEmployee(null, 23);
mimacomEmployee.getUserName().contains("albert")
Hasta ahora, solamente contábamos con la línea de la clase en la que se había producido esta excepción:
Exception in thread "main" java.lang.NullPointerException
at MimacomEmployeeHandler.main(MimacomEmployeeHandler.java:45)
Con la inclusión de esta mejora en JDK 14, el mensaje sobre el NullPointerException producido es:
Exception in thread "main" java.lang.NullPointerException:
Cannot read field 'userName' because 'mimacomEmployee.userName' is null.
at MimacomEmployeeHandler.main(MimacomEmployeeHandler.java:45)
Mejoras en la expresión Switch (Standard Feature)
Esta funcionalidad llegó como una Preview Feature en Java 13, y se ha consolidado como completada en la versión 14. Básicamente mejora la legibilidad de las expresiones 'Switch' permitiéndonos usar "->" en los condicionales:
switch(mimacomEmployee.getDayOfWork()) {
case MONDAY, TUESDAY, THURSDAY -> LOGGER.info("0, 1, 3");
case SATURDAY -> LOGGER.info("5");
case WEDNESDAY -> LOGGER.info("2");
}
Nuevas secuencias de escape en Text Block (Second Preview)
Un ejemplo de una preview feature añadida en la versión 13 y que ha gustado a la comunidad que se ha convertido en una Second Preview de Java 14. Se han añadido nuevas secuencias de escape para dotar de más funcionalidad a los Text Block.
Text Block (Preview feature de Java 13) nos permitía añadir código que normalmente necesita de un escapado con el delimitador de 3 comillas dobles “”” haciendo más legible el código, como por ejemplo código HTML, javascript o una consulta SQL:
String query = """
SELECT 'emp_id', 'username' FROM flowable_users
WHERE 'role' = 'Software Engineer'
ORDER BY 'emp_id';
""";
Java 14 incluye nuevas secuencias de escape que permiten añadir más funcionalidad a los text block, en concreto:
- \: Obliga a la creación de una nueva línea separadora
- \s: Evita la eliminación del espacio en blanco al final de cada línea.
JFR Event Streaming (Standard feature)
Solo hay dos tipos de desarrolladores Java, aquellos que ya han tenido que hacer profiling y aquellos que lo tendrán que hacer en el futuro.
Por eso, si has llegado a este post, probablemente hayas usado:
- Java Flight Recorder (JFR): El motor que escribe eventos en formato binario del comportamiento de la JVM.
- Java Mission Control (JMC): Esa interfaz gráfica que utilizamos para examinar los eventos escritos por JFR.
Estas herramientas a partir de Java 11 se convirtieron en herramientas de código abierto haciendo posible su descarga por separado.
Java 14 introduce una nueva característica en JFR, y es la capacidad producir eventos en streaming. Ya podéis imaginar, que si JFR crea un flujo continuo de eventos, Java debe de contar con una API que permita tanto interactuar como suscribirse a los eventos de inmediato, en lugar de analizar un archivo binario después de que los eventos hayan sido creados.
Así ha nacido JFR Event Streaming, la nueva API que provee interfaces como jdk.jfr.consumer.EventStream que abren un nuevo mundo en las herramientas de monitorización estableciendo como base el consumo de estos eventos en streaming.
Nueva API Foreign-Memory Access API (Incubator)
Para comprender el siguiente cambio, hay que entender las distintas capas de memoria de Java, principalmente para este caso, basta con diferenciar entre 2 capas:
- Memoria Heap: Básicamente es la memoria dentro de la JVM que es utilizada para contener los objetos Java y que mantiene el recolector de basura.
- Memoria Foreign (Off-heap): Memoria destinada a gestionar objetos (serializados) administrados por la caché, que ni están ni en el Heap, ni son mantenidos por el recolector de basura. En este tipo de memoria englobamos native memory, persistent memory, managed heap memory…
Explicado este punto, en Java 14 se ha añadido una nueva API, Foreign-Memory Access API, como una mejora de tipo incubator que mejora el rendimiento al acceder a la foreign memory. Ya existen multitud de librerías externas que acceden a esta memoria (java.nio.ByteBuffer, JNI, MemCache…), pero con esta API y sus nuevas abstracciones: MemorySegment, MemoryAddress and MemoryLayout no será necesaria la inclusión de una librería externa para acceder a esta memoria, ya que el objetivo de esta API es que de forma nativa se pueda acceder de forma eficiente y segura a la foreign memory.
Recolectores de basura (Standard feature)
En los lanzamientos de las últimas versiones, los cambios en los recolectores de basura han tenido un papel protagonista, por ejemplo, con el lanzamiento de la versión de Java 9, el recolector de basura por defecto pasó de ser Parallel GC a G1. Pues con el lanzamiento de esta nueva versión 14, el ya deprecado recolector CMS (Concurrent Mark Sweep) ha sido eliminado por completo. Si al migrar a Java 14 se sigue utilizando XX:+UseConcMarkSweepGC, Java nos lo indicará con el siguiente warning:
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; support was removed in <version>
También se han producido mejoras significativas en el rendimiento general del G1 en los accesos a la memoria no uniforme (NUMA: Non-Uniform Memory Access).
Si la JVM es arrancada con +XX:+UseNUMA, las regiones en las que divide la memoria el G1 se distribuirán de manera uniforme por todos los nodos NUMA disponibles, evitando tener que asignar el nodo NUMA de cada región cuando la máquina virtual es inicializada. Cuando el recolector de basura G1 necesite asignar una nueva región (como asignar un objeto a un hilo) lo hará seleccionando preferentemente una del mismo nodo NUMA al que está vinculado el hilo actual, manteniendo así este objeto en el mismo nodo NUMA del hilo y mejorando así su rendimiento.
Conclusión
Además de estas mejoras, hay muchas otras features desarrolladas (muchas de ellas han surgido por petición de la comunidad) en los 6 meses de duración desde el último lanzamiento de Java. Java 14 es una versión bastante interesante, porque a pesar de ser una versión 'lite', es una versión que está justo a mitad de camino entre 2 versiones LTS y ayudan a comprender qué previews han sido rechazadas en la versión anterior y cuáles están siendo evolucionadas, dándonos una idea de la forma que tendrá Java 17 y ayudándonos así a definir una estrategia de migración que reduzca el riesgo del proceso.
¡Nos vemos en futuras releases!