Novedades de Java 16

Escrito por el .
java planeta-codigo
Enlace permanente Comentarios

En Java 16 los cambios en el lenguaje no son tan notables que en versiones anteriores, aún así el calendario se sigue manteniendo y en cada versión se incluyen mejoras incrementales destacables. Se publica la versión final y lista para producción de algunas características y se añaden nuevas en forma de vista previa.

Las nuevas versiones se van sucediendo de forma periódica y puntual según la cadencia de seis meses del adoptado calendario de publicación. En esta ocasión la versión 16 de Java publicada el 16 de marzo de 2021, que incluye mejoras incrementales destacando soporte para nuevas plataformas, inclusión de forma final de características que anteriormente estaban marcadas como vista previa y algunas nuevas características destacables que entran en forma de vista previa y nuevas revisiones de vistas previas anteriores.

La versión 16 de Java será la última antes de la siguiente versión de soporte a largo plazo o LTS, la versión 17 será una versión LTS que sucederá a la anterior LTS publicada de Java 11. La versión 11 fue publicada en septiembre de 2018 y la 17 será publicada en septiembre de 2021.

Introducción

No hay cambios importantes en el lenguaje, el más destacado es la inclusión de pattern matching para el operador instanceof, la incorporación de forma final de los Records, soporte para las plataformas Windows/AArch64, distribuciones como Alpine basadas en musl y soporte para la comunicación mediante canales Unix-Domain Socket.

En los cambios que entran en modo vista previa destacan la Vector API que permite la computación en paralelo aprovechando las instrucciones vectoriales en los procesadores como AVX en x86 y Neon en ARM para acelerar las operaciones de una instrucción múltiples datos o SIMD. Aparte de los destacados hay números cambios más realizados en las APIs de las clases y el añadido de algunas funcionalidades menores, que en las notas de publicación están documentados.

Las mejoras incluídas en esta versión son:

Nuevas características

Habilitar características de C++14

Hasta el JDK 15, las características usadas por el código C++ en el JDK ha estado limitado a los estándares de C++98/02. Con el JDK 11, el código fué actualizado para soportar nuevas versiones del estándar C++, aunque hasta ahora no han sido usadas nuevas características. Esto incluye poder construirse con versiones recientes de varios compiladores que soportan características de C++11/14.

Ahora se permiten formalmente cambios en el código C++ del JDK para aprovechar las características del lenguaje C++14.

Migración de Mercurial a Git

Se migra como herramienta de control de versiones de Mercurial a Git. Esto incluye todos los repositorio de los proyectos de OpenJDK preservando el historial, incluyendo las etiquetas y formateando los mensajes de commits según las convenciones de Git.

Migración a GitHub

Relacionada con la migración de Mercurial a Git es la migración a la plataforma GitHub como proveedor de hospedaje para los repositorios. Los diferentes proyectos del OpenJDK están accesibles en GiHub.

Los beneficios de esta migración son tiempos más reducidos en el clonado y obtención de cambios, mayor disponibilidad de los repositorios, posibilidad de interactuar con los repositorios mediante listas de correo, herramientas de línea de comandos y navegadores web.

Usar un proveedor de hospedaje externo libera a los contribuidores de implementar y administrar el servicio. Las tres razones principales de usar un proveedor externo son el rendimiento, acceso a una API para automatizar las tareas y acceso a una mayor comunidad que proporcione incorpore nuevos contribuidores.

La razón de usar GitHub es que destaca en las tres razones principales de usar un proveedor externo.

ZGC: procesado concurrente de pila del thread

El recolector de basura ZGC tiene como objetivo hacer que las pausas de recolección de basura sean una cosa del pasado. Muchas de las operaciones del recolector de basura ya se han hecho ya escalables al tamaño de la memoria y del metaespace.

Las únicas tareas que son realizadas en puntos seguros del recolector de basura son un subconjunto de procesado raíz. Una de ellas son las pilas de trazas de excepciones. Estas tareas de procesado raíz son problemáticas escalan con el número de hilos, en sistemas con muchos hilos estas tareas se convierten en un problema.

Para conseguir el objetivo de que las pausas seguras del recolector de basura no excedan de un milisegundo, incluso en máquinas grandes, se ha de mover este procesado por hilo incluyendo la inspección de la pila de trazas a una fase concurrente.

Después de los cambios nada significativo se hará dentro de las pausas seguras del recolector de basura.

Canales de comunicación Unix

Se ha añadido soporte para la comunicación mediante sockets Unix a las APIs del paquete java.nio.channels. Los sockets Unix con una forma de comunicación entre procesos o IPC en la misma máquina. Son similares a los sockets TCP/IP en la mayoría de aspectos excepto que son resueltos por nombres del sistema de archivos en vez de direcciones IP y números de puertos. La comunicación mediante sockets Unix es más segura y eficiente que las conexiones TCP/IP.

  • Las comunicaciones son para procesos en el mismo sistema, las aplicaciones que no tiene intención de aceptar conexiones remotas pueden mejorar la seguridad con los sockets Unix.
  • Los sockets Unix están protegidos por los mecanismo de seguridad del sistema de archivos.
  • Los sockets Unix son más rápidos de establecer y ofrecen mayor tasa de transferencia.

Los sockets Unix son una característica implementada en la mayoría de sistemas Unix y son soportados en Windows 10 y Windows Server 2019.

Los elementos de la API son java.net.UnixDomainSocketAddress, java.net.StandardProtocolFamily, SocketChannel y ServerSocketChannel.

Más detalles en JEP-380: Unix domain socket channels

Unix Sockets

Unix Sockets

Portado a Alpine Linux

Se ha portado el JDK a Alpine Linux y otras distribuciones que usan musl como librería C en las arquitecturas x64 y AArch64. musl es una implementación para los sistemas basados en Linux de la librería estándar con su funcionalidad descrita en los estándares de ISO C y POSIX. Varias distribuciones Linux incluyendo Alpine Linux y OpenWrt están basadas en musl, mientras que otras proporcionan soporte de forma opcional como Arch Linux.

La distribución Alpine Linux es una distribución ampliamente adoptada en los entornos en la nube, microservicios y contenedores debido a su pequeño tamaño de imagen. La imagen base de Alpine Linux por ejemplo es de unos 6 MB lo que la hace atractiva por sus tiempos de arranque, ancho de banda más bajos y mayor seguridad al incluir menos componentes. Habilitar Java para funcionar en estos entornos permite ejecutar de forma nativa Tomcat, Jetty, Spring y otros frameworks populares.

Complementandlo con jlink para reducir el tamaño en tiempo de ejecución del JDK un usuario puede crear imágenes más pequeñas. Por ejemplo, si la aplicación depende solo del módulo java.base entonces una imagen de Docker con Alpine Linux y un runtime de Java con solo ese módulo y la máquina virtual de servidor cabe en tan solo 38 MB.

Metaspace elásctico

La memoria no usada de metaespace es retornada al sistema operativo con más rapidez reduciendo el tamaño del metaspace y simplificando el código de metaspace con la intención de reducir los costes de mantenimiento.

Portado a Windows/AArch64

Con la disponibilidad del nuevo hardware de consumo y servidor basado en la arquitectura AArch64 (ARM64), Windows/AArch64 se ha convertido en una plataforma importante debido a la demanda de los usuarios. Se ha portado el JDK a Windows/AArch64 continuando el trabajo anterior para la portabilidad de Linux/AArch64 (JEP 237) y en un futuro se hará lo equivalente para macOS.

Mensajes de advertencias para clases valor

Las clases envoltorio o wrapper de los tipos primitivos se designan como clases valor, ahora se emiten nuevos mensajes de advertencia en sus constructores marcados como deprecated desde Java 9 y candidatos a ser eliminados en futuras versiones.

Las clases wrapper de primitivos incluyen las Integer, Float, Double, etc., estas clases satisfacen la mayor parte de los requerimientos de las clases valor con la excepción de que exponen los constructores marcados como obsoletos.

Esto es un objetivo para el proyecto Valhalla de implementar clases primitivas que mejoran el modelo de programación de Java.

Packaging Tool

Se proporciona una herramienta para empaquetar aplicaciones Java que genera instaladores nativos para las diferentes plataformas. La herramienta jpackage fue incorporada en Java 14 y ahora pasa a considerarse con la categoría de listo para producción, su API está en el jdk.jpackage.

Soporta los formatos de empaquetado nativos de la plataforma para tener una experiencia de instalación natural. Estos formatos incluyen archivos msi y exe en windows, pkg y dmg en macOS, deb y rpm en Linux. Permite especificar en tiempo de empaquetado parámetros a usar en tiempo de ejecución. Se puede invocar de forma directa desde la línea de comandos y de forma programática mediante su API.

Pattern Matching para instanceof

Se mejora el soporte del lenguaje para soportar pattern matching en el operador instanceof. Esto evita la necesidad de realizar cast de forma explícita simplificando el código, más legible y seguro.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Antes
if (obj instanceof String) {
   String s = (String) obj;
   System.out.println(s);
}

// Ahora, con pattern matching
if (obj instanceof String s) {
   System.out.println(s);
}
if (obj instanceof String s && s.contains("jdk")) {
   System.out.println(s);
}
instanceof-pattern-matching-1.java

El uso de pattern matching reduce la necesidad de hacer cast de forma explícita siendo particularmente útil en los métodos de igualdad con equals.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Antes
public boolean equals(Object o) {
   if (!(o instanceof Point))
       return false;
   Point other = (Point) o;
   return x == other.x
       && y == other.y;
}

// Ahora, con pattern matching
public boolean equals(Object o) {
   return (o instanceof Point other)
       && x == other.x
       && y == other.y;
}
instanceof-pattern-matching-2.java

Records

Las clases Record son un nuevo tipo de clases en el lenguaje Java, ayudan a modelar agregados de datos con menos ceremonia que las clases normales. Son clases que actúan como contenedores para datos inmutables, pueden ser considerados como tuplas. La declaración de un record mayormente consiste en la declaración de su estado. No es su objetivo resolver los problemas de las clases mutables que usan las convenciones de nombres de los JavaBeans.

Se publicaron como primera vista previa en Java 14 y una segunda versión en Java 15.

Las siguientes dos versiones de la clase Point son equivalentes.

1
2
public record Point(int x, int y) {}

records-1.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Point {
   private final int x;
   private final int y;

   Point(int x, int y) {
       this.x = x;
       this.y = y;
   }

   int x() { return x; }
   int y() { return y; }

   public boolean equals(Object o) {
       if (!(o instanceof Point)) return false;
       Point other = (Point) o;
       return other.x == x && other.y == y;
   }

   public int hashCode() {
       return Objects.hash(x, y);
   }

   public String toString() {
       return String.format("Point[x=%d, y=%d]", x, y);
   }
}
records-2.java

Encapsulación fuerte del clases internas del JDK por defecto

Se cambia el comportamiento por defecto de la encapsulación de permitido a fuerte para las clases internas del JDK aunque siguiendo posible relajar el comportamiento de la encapsulación con la opción de línea de comandos –illegal-access=permit, la encapsulación fuerte hace una excepción para ciertas clases internas como sun.misc.Unsafe que siguen siendo posible utilizarlas. Esto permite continuar con la encapsulación y mantenibilidad del JDK que era el objetivo del proyecto Jigsaw materializado en la incorporación de los módulos en Java 9. Se promueve el uso de APIs estándares para actualizaciones a futuras versiones sin problemas.

A lo largo de los años los desarrolladores han estado usando clases internas del JDK de los paquetes java.*, sun.*. Nunca se ha aconsejado pero nada impedía su uso hasta la aparición de los módulos. El uso de las clases de estos paquetes supone un problema de seguridad y mantenibilidad en el JDK pero impedir de forma drástica su uso haría que muchas librerías y aplicaciones que las usan dejasen de funcionar.

Ahora se encapsula de forma más fuerte las API internas que aplica tanto en tiempo de compilación como de ejecución, incluyendo los intentos del código de acceder a elementos mediante reflection.

Nuevas características en vista previa

Vector API

El objetivo es proporcionar una API para aprovechar las instrucciones vectoriales hardware de las arquitecturas de CPU que permiten conseguir un mejor rendimiento a las computaciones equivalentes escalares. Las instrucciones vectoriales son conocidas como Single Instruction Multiple Data (SIMD) que permiten mayor paralelismo al aplicar la misma operación a múltiples datos en un único ciclo de reloj de la CPU.

Su objetivo es proporcionar una API clara y concisa, agnóstica de la plataforma, de alto rendimiento para las arquitecturas x64 y AArch64 y con soporte de degradado en caso de no estar las instrucciones disponibles.

La API de vectorial es muy útil para funcionalidases como procesado de imágenes, procesado de sonido, gráficos inteligencia artificial que necesitan realizar la misma operación sobre muchos datos.

Foreign Linker API

El objetivo es ofrecer una API con tipado estático y código Java para acceso a código nativo. Junto con Foreign-Memory Access API simplifica considerablemente el proceso propenso a errores de enlazado a una librería nativa.

Sus objetivos son facilidad de uso reemplazando JNI con un modelo de desarrollo superior basado puramente en Java, soportar C en las plataformas x64 y AArch64, soportar otras plataformas como 32-bit x86 y otros lenguajes distintos de C como C++ o Fortran y ofrecer un alto rendimiento comparable o mejor que JNI.

Foreign-Memory Access API

Su objetivo es proporcionar a los programas Java una forma segura y eficiente de acceder a memoria fuera de la memoria de la máquina virtual de Java.

Sealed Classes

Las clases selladas o sealed classes permiten al autor de la clase o interfaz controlar que código es responsable de implementarlo. Proporcionan una forma de restringir el uso de la superclase distinta de los modificadores de acceso. En un futuro puede ser la base para implementar pattern matching para un análisis exhaustivo de patrones.

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


Comparte el artículo: