Novedades y nuevas características de Java 17

Escrito por el .
java planeta-codigo
Enlace permanente Comentarios

La versión Java 17 sucede a Java 11 como versión LTS, por ello es una versión más importante que las no LTS anteriores. Incorpora todas las mejoras incluidas en todas las no LTS previas más otras adicionales dede Java 16 publicada seis meses antes. Como versión LTS ofrece un soporte de correcciones de errores, fallos y alertas de seguridad por un periodo de cinco años hasta septiembre de 2026 más un periodo adicional de tres años hasta 2029. La versión 6 del framework de Spring y Spring Boot 3 se tendrán como requisito mínimo Java 17 y Jakarta 9.

Java

El ciclo de publicación de una nueva versión de Java cada seis meses está siendo un éxito en la evolución del lenguaje, algunas características no tienen un gran impacto en la plataforma o el lenguaje pero otras sí suponen un gran mejora como las lambdas de Java 8, los módulos de Java 9, inferencia de tipos de Java 10, Java 11 como primera versión LTS con soporte a largo plazo, expresiones switch de Java 12 en vista previa, bloques de texto de Java 13 en vista previa, excepciones NullPointerException más útiles y Records de Java 14, Sealed Classes de Java 15 y encapsulación más fuerte de clases internas del JDK en Java 16 por mencionar simplemente una característica destacada de cada una de ellas.

Java 17 al ser una versión LTS es más importante que las versiones no LTS anteriores. Las versiones LTS son más atractivas para ciertas organizaciones por sus periodos de soporte extendidos.

Introducción

Java 17 sucede a como versión LTS en septiembre de 2021 a Java 11 que fué publicada hace tres años en septiembre de 2018. Java 17 según la hoja de ruta de Java tiene un soporte de cinco años hasta septiembre de 2026 y tres años más hasta septiembre de 2029 en el periodo de soporte extendido para los clientes que paguen ese soporte.

Este calendario de soporte claro y simple permite planificar la estrategia de actualización de forma anticipada de las aplicaciones que usen versiones que han dejado de estar soportadas. Las actualizaciones incluyen correcciones de errores, fallos y alertas de seguridad, actualizaciones legales, regulación y de impuestos asi como certificación con otros productos. El software empresarial por ciclo de vida prefiere usar versiones LTS por su soporte extendido, con la publicación de Java 17 está es la versión recomendada.

En el caso de pasar de la versión 11, anterior LTS, o previas, la versión 17 incluye las numerosas novedades de las versiones no LTS y previa LTS a esta, de la 12 a la 16.

Spring ha anunciado que la versión 6 de este framework junto a Spring Boot 3 ampliamente usados se basarán en Java 17 y Jakarta EE 9. Estas versiones serán la base mínima requerida de la siguiente generación de aplicaciones Java.

Spring 6 y Spring Boot 3 se publicará a finales del 2022 cuando Java 17 incluso ya tenga un par de versiones sucesoras. Los entornos integrados de desarrollo como IntelliJ IDEA han sido actualizados para proporcionar soporte desde el primer momento a las nuevas características de Java 17.

Las mejoras incluidas en esta versión son:

Nuevas características

Restaurar la semántica estricta de coma flotante

El estándar IEEE 754 especifica cómo realizar operaciones de coma flotante y cómo almacenar valores de coma flotante en varios formatos incluyendo en precisión simple (en 32 bits usado en el float de Java) y precisión doble (en 64 bits usado en el double de Java).

Algunos dispositivos hardware como CPU proporcionan precisiones extendidas con mayor precisión o mayor rango del exponente. En esas arquitecturas con precisiones extendidas puede ser más eficiente usarlas para los resultados intermedios evitando operaciones de redondeo o desbordamientos que de otra manera ocurrirían. Esto hace las operaciones más eficientes pero puede ocasionar resultados diferentes en esas arquitecturas. La precisión extendida en las máquinas x86 con la arquitectura x87 por sus peculiaridades era necesaria ser usada para evitar penalizaciones de rendimiento.

Antes de las versiones a la JVM 1.2 los cálculos de coma flotante requerían ser estrictas empleando la misma precisión tal como define el estándar IEEE 754 para todos los cálculos intermedios. La semántica estricta de coma flotante era costosa en el hardware x87 por ello en la versión de la JVM 1.2 se cambió la semántica estricta por defecto por una semántica con precisión extendida produciendo resultados posiblemente más precisos pero a riesgo de resultados menos repetibles entre diferentes arquitecturas.

Dado que los cálculos de coma flotante mediante x87 ya no son necesarios ni tienen penalización de rendimiento en los procesadores x86 que soportan el conjunto de instrucciones SSE2 introducidas en el Pentium 4 y están al mismo tiempo presentes en los procesadores de AMD y sucesores de Intel, Java 17 de nuevo hace que todas operaciones de coma flotante sean estrictas recuperando de forma efectiva el comportamiento de las versiones anteriores a la JVM 1.2.

Generadores de números pseudo-aleatorios mejorado

En la API de Java hay varias clases que permiten la obtención de números pseudo-aleatorios, estas clases contienen diferentes métodos con código repetido en varias de las implementaciones.

Se proporciona una nueva interfaz RandomGenerator que proporciona una API uniforme para los generadores de números aleatorios existentes. RandomGenerator proporciona métodos para ints, longs, y doubles con sus variaciones de parámetros. También se proporciona la factoría RandomGeneratorFactory para localizar y construir instancias que implementan la interfaz RandomGenerator usando la API de ServiceLoader.Provider para registrar las implementaciones.

1
2
3
import java.util.random.RandomGeneratorFactory;

RandomGeneratorFactory.of("Random").create().nextInt(0, 11);
RandomGeneratorFactory.java

Nuevo pipeline de renderizado para macOS

Se implementa un pipeline de renderizado interno de Java 2D para macOS usando Apple Metal como alternativa al pipeline existente que usa el obsoleto Apple OpenGL API estando preparado para cuando Apple lo elimine.

Las funcionalidades proporcionadas en el pipeline usando Apple Metal API son equivalentes a las existentes en OpenGL con un rendimiento tan bueno o mejor. Ambos pipelines coexistirán hasta que el pipeline OpenGL se considere obsoleto.

Portado a macOS/AArch64

En Java 16 se implementó el portado a AArch64 para Windows, ahora se hace lo equivalente para macOS. Dado que ARM es una plataforma que será más común con el tiempo junto con el anuncio de Apple de su plan a largo plazo de transición de la arquitectura x64 a AArch64 se espera una demanda amplia para el portado de macOS/AArch64 del JDK.

Marcado como obsoleto para eliminación la Applet API

Se marca como obsoleta para eliminación la de los Applet. A día de hoy es irrelevante dado que todos los vendedores de navegadores web han eliminado el soporte para los complementos de Java o han anunciado planes para hacerlo.

Anteriormente en Java 9 con la Applet API fué marcada como deprecada aunque no para eliminación.

La alternativa similar a los Applets es utilizar Java Web Start, que permitía descargar y lanzar aplicaciones Java como aplicaciones de escritorio sin utilizar el navegador como entorno de ejecución. En Java 9 Java Web Start fué marcado como obsoleto y en Java 11 el su soporte fué eliminado. La alternativa equivalente a los Applets y Java Web Start es OpenWebStart.

Encapsulación fuerte de las clases internas del JDK

Se encapsula de forma fuerte impidiendo su uso de todos los elementos internos del JDK de los paquetes java.*, sun.*, com.sun.*, jdk.* y org.* , exceptuando ciertas APIs críticas como sun.misc.Unsafe. En el JDK 16 se cambió el comportamiento por defecto de permitido a fuerte aún siguiendo siendo posible utilizar la opción para relajar la encapsulación. Ya no será posible relajar la encapsulación de los elementos internos mediante la opción de línea de comandos –illegal-access=permit como era posible en el JDK 9 hasta el JDK 16.

La encapsulación fuerte permite mejorar la seguridad y el mantenimiento del JDK que era uno de los objetivos primarios del proyecto Jigsaw con la incorporación de los módulos. Se aconseja a los desarrolladores migrar del uso de elementos internos a APIs estándar de modo que tanto los desarrolladores de librerías como sus usuarios puedan actualizar sin complicaciones a futuras versiones de Java.

Eliminación de RMI Activation

Se elimina el mecanismo de activación de RMI mientras se conserva el resto de RMI. El mecanismo de llamada a procedimiento remoto RMI de Java es una tecnología obsoleta prefiriéndose REST para la integración de sistemas distribuidos, RMI también ha sido superado y mejorado por gRPC.

Clases sealed

Las clases sealed fueron propuestas en Java 15 en modo vista previa, en Java 16 fueron propuestas de nuevo con algunos cambios. Las clases sealed son incorporadas de forma final sin cambios respecto a Java 16. Las clases sealed permite limitar que que clases tienen permitido heredar de una clase definida como sealed.

1
2
3
4
5
public abstract sealed class Shape permits Circle, Rectangle, Square { ... }

public class Circle extends Shape { ... }
public class Rectangle extends Shape { ... }
public class Square extends Shape { ... }
sealed-classes.java

Eliminar el compilador experimental AOT y JIT

Se elimina el soporte experimental de compilación ahead-of-time o AOT y el compilador just-in-time o JIT implementados en Java. Este compilador ha sido usado poco desde su introducción suponiendo un esfuerzo de mantenimiento significativo. Se mantiene la interfaz experimental de compilador JVM basado en Java o JVMCI para que los desarrolladores continúen construyendo externamente versiones del compilador para la compilación JIT.

La herramienta jaotc fue incorporada en el JDK 9 de forma experimental, usa el compilador Graal que está escrito a su vez en Java. El compilador Graal fue incorporado como compilador JIT experimental en el JDK 10. Los desarrolladores que deseen usar el compilador Graal para realizar compilación AOT o JIT pueden usar GraalVM.

Marcado como obsoleto para eliminación el Security Manager

El Security Manager está presente desde la versión de Java 1.0. No ha sido la forma de añadir seguridad en el código Java en el lado cliente desde hace muchos años y ha sido raramente usado para añadir seguridad en el código de lado del servidor. Se marca como obsoleto el Security Manager en consonancia con la Applet API heredada.

El control de acceso se basa en el principio de menor privilegio que era viable en la reducida librería de clases de Java 1.0 pero con el rápido crecimiento de los paquetes java.* y javax.* ha originado docenas de permisos y cientos de comprobaciones de permisos en todo el JDK. Esto supone un área grande a mantener seguro.

Ahora se considera que la seguridad es mejor implementarla proporcionando integridad a bajo nivel en la plataforma Java, por ejemplo fortaleciendo los límites de los módulos para prevenir acceso a detalles del JDK, y aislando el entorno de ejecución de Java completo de recursos sensibles mediante mecanismos ajenos al proceso o out-of-process como los contenedores y virtualización.

Se marca como obsoleto el Security Manager y eliminan algunas de sus capacidades a lo largo de varias versiones y simultáneamente se crean APIs alternativas para ciertas tareas como bloquear la llamada a System::exit u otros casos de uso considerados suficientemente importantes para tener reemplazos.

En la JEP 411 hay una descripción detallada de las motivaciones de esta eliminación además de su poco uso se proporciona varias deficiencias como un modelo de permisos frágil, un modelo de programación complicado que desincentiva su uso y bajo rendimiento. Tampoco es eficaz para evitar la mayoría de problemas de seguridad más importantes identificados en 2020.

Filtros de deserialización específicos para cada contexto

Esto permite a las aplicaciones configurar un filtro de deserialización específico según el contexto y seleccionado dinámicamente mediante una factoría de filtros de la JVM para cada operación de deserialización.

Nuevas características en vista previa

Además de las características anteriores se incorporan otras en modo experimental que también se pueden usar pero que podrían cambiar en el futuro.

Pattern Matching para los switch

Se mejoran las sentencias y expresiones switch de dos formas:

  • Se extienden las etiquetas case para incluir patrones adicionalmente a constantes.
  • Se añaden dos nuevos tipos de patrones: patrones de protección o guarded patterns y patrones con paréntesis o parenthesized patterns.

Este es un ejemplo de sentencia if-else de varios niveles con expresiones booleanas usando el operador instanceof, gracias a que el operador instanceof soporta pattern matching se evita los cast de Object al tipo del instanceof, sin embargo, el código de la sentencia if-else sigue siendo de difícil lectura.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Object o = 123L;
String formatted = null;
if (o instanceof Integer i) {
    formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
    formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
    formatted = String.format("double %f", d);
} else if (o instanceof String s) {
    formatted = String.format("String %s", s);
} else {
    formatted = o.toString();
}
switch-pattern-matching-1.java

Este es un ejemplo de expresión switch que usa pattern matching para las etiquetas case que sustituye una expresión if-else anterior.

1
2
3
4
5
6
7
8
Object o = 123L;
String formatted = switch (o) {
    case Integer i -> String.format("int %d", i);
    case Long l -> String.format("long %d", l);
    case Double d -> String.format("double %f", d);
    case String s -> String.format("String %s", s);
    default -> o.toString();
};
switch-pattern-matching-2.java

La etiqueta case también soporta la comprobación del valor null que ha de ser especificado explícitamente, para mantener la compatibilidad hacia atrás el default case no cumple con el valor null.

1
2
3
4
5
switch (o) {
    case null -> System.out.println("null!");
    case String s -> System.out.println("String");
    default -> System.out.println("Something else");
}
switch-pattern-matching-3.java

En el primer caso de esta expresión switch se hace uso de una patrón de protección y de un patrón con paréntesis.

1
2
3
4
switch (o) {
    case String s && (s.length() == 1) -> ...
    case String s -> ...
}
switch-pattern-matching-4.java

Un patrón de protección tiene la forma p && (e) donde p es un patrón y e es una expresión booleana, este patrón incluye la unión de todas las variables definidas en el patrón p y la expresión e. Una valor cumple con un patrón de protección si primero cumple el patrón p y segundo la expresión e se evalúa como verdadero, si el valor no cumple p no se evalúa la expresión e.

Una patrón con paréntesis tiene la forma (p), donde p es un patrón. Una patrón con paréntesis introduce las variables de patrón que son introducidas por el subatrón p. Un valor cumple el patrón parametrizado (p) su cumple el patrón p.

Foreign Function & Memory API

La Foreign Function & Memory API o FFM API permite que los programas Java puedan interoperar con código y datos fuera del entorno de ejecución de Java, invocar eficientemente funciones externas (fuera de la JVM) y acceder de forma segura memoria externa (no gestionada por la JVM).

La FFM API permite a los programa Java llamar a librerías nativas y procesar datos nativos sin la fragilidad y peligro de JNI. Esta es una revisión introducida inicialmente en Java 14 y revisada en Java 15 y 16.

La API en el módulo jdk.incubator.foreign define varias clases e interfaces para que el código cliente en librerías puedan:

  • Solicitar memoria externa.
  • Manipular y acceder a estructuras de memoria externas.
  • Gestionar el ciclo de vida de los recursos externos.
  • Invocar funciones externas.

Vector API

Se añade la Vector API para expresar computaciones vectoriales que son compiladas en tiempo de ejecución en las instrucciones vectoriales de las arquitecturas de CPU soportadas, esto permite conseguir un rendimiento superior al equivalente con las computaciones escalares.

La Vector API fue integrada en Java 16 en forma de incubación, en esta nueva revisión se incorporan mejoras en respuesta a los comentarios así como a mejoras de rendimiento y otras mejoras significativas en la implementación.

Adelante más rápido

Como última nota se está proponiendo lanzar una versión LTS cada dos años en vez de cada tres lo que permitirá a aquellos usuarios que prefieren el soporte extendido tener disponible una versión LTS cada menos tiempo proporcionando más oportunidades de actualización. Por otro lado, hace más atractivas las versiones no-LTS pudiendo los desarrolladores comenzar con una versión no LTS sabiendo que en dos años habrá una versión LTS que poder utilizar en producción.


Comparte el artículo: