Java 21 Features – Guía Rápida

Advertisements

Java 21, una versión LTS (soporte a largo plazo) se lanzó el 19 de septiembre de 2023. Incluye características muy interesantes que han ido evolucionando en versiones anteriores. Al igual que con las versiones LTS anteriores (11, 17), incluye que podrían estar listas en Versiones anteriores no LTS (18,19,20). Veamos algunas de las características más importantes e interesantes incluidas en esta nueva versión.

Sequenced Collections

Java Collections carece de un tipo de colección para representar una secuencia de elementos con un orden de encuentro definido y operaciones uniformes entre ellos. Algunas personas podrían pensar, pero tiene List y ArrayList, sin embargo, carece de métodos para obtener o agregar elementos en algún orden, list.get(0) o list.get(list.size()-1) o list.add(0, elemento) no es suficiente. O quitar la cola con los métodos getFirst o getLast. Pero, en el caso del propósito de dequeue debe ser una pila, una colección con otro propósito.

Además de eso, la interfaz de Collection es demasiado genérica para incluir métodos que proporcionen colecciones secuenciadas, por lo que se han agregado algunas interfaces nuevas.

– Sequenced Collection

interface SequencedCollection<E> extends Collection<E> {
    SequencedCollection<E> reversed();
    void addFirst(E);
    void addLast(E);
    E getFirst();
    E getLast();
    E removeFirst();
    E removeLast();
}

– SequencedSet

interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();    // covariant override
}

– SequenceMap

interface SequencedMap<K,V> extends Map<K,V> {
    // new methods
    SequencedMap<K,V> reversed();
    SequencedSet<K> sequencedKeySet();
    SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
    V putFirst(K, V);
    V putLast(K, V);
    // methods promoted from NavigableMap
    Entry<K, V> firstEntry();
    Entry<K, V> lastEntry();
    Entry<K, V> pollFirstEntry();
    Entry<K, V> pollLastEntry();
}

Con las nuevas tres interfaces agregadas, la jerarquía de colecciones ha cambiado para incluirlas.

Por lo tanto, las implementaciones de List y Deque ahora son colecciones secuenciadas correctamente. En el lado de Set: SortedSet, LinkedHashSet ahora implementa la interfaz SequenceSet. Y finalmente, en la interfaz Map, SortedMap ahora implementa SequencedMap.

Por lo tanto, algunas implementaciones que en el pasado los desarrolladores de Java consideraban secuenciales, ahora están correctamente definidas como tal. En mi opinión, la introducción de un orden de encuentro bien definido y una API uniforme en todos los ámbitos es una adición bienvenida a Java para los desarrolladores quienes una forma más sencilla de simplificar las tareas de recopilación de bienes comunes y, poco a poco. Java añade más comodidad a sus tipos.

Virtual Threads

Respecto a los Thread Virtuales hay un artículo más completo aquí, es recomendable lo puedan revisar. Project Loom comenzó en 2017, como una iniciativa para proporcionar hilos ligeros que no están ligados a los hilos del sistema operativo sino que son administrados por la JVM.” Los “Threads de Plataforma” son costosos en cuanto a recursos, por otro lado los “Threads Virtuales” son livianos y manejados por la JVM, lo que hace posible tener millones de ellos, el único límite es la memoria de la JVM.

var thread = Thread.ofVirtual().start(() -> {
  System.out.println("Hello World!");
});

Los thread virtuales son compatibles con otras API del JDK (solicitud HTTP, sockets, programación asíncrona), por lo que su uso aprovechará la optimización del tiempo de inactividad (esperando una respuesta) y permitirá cambiar a otros thread virtuales.

También cuenta con soporte con el Executor Service, para proporcionar un “executor” que crea un nuevo Thread Virtual en cada ejecución.

ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
executorService.execute(() -> {
  System.out.println("Hello World!");
});

Debido a que los “virtual threads” son livianos y se pueden crear miles o millones, no se recomienda crear un “pool de virtual threads”, no habrá optimización al hacerlo. Simplemente cree el thread virtual, úselo, deséchelo y luego cree otros nuevos, dado qué el servicio ejecutor crea un nuevo hilo virtual por ejecución.

Otra consideración es que, si su lógica requiere mucha CPU, no tiene sentido usar threads virtuales. Al final, hay un límite en los núcleos físicos, los threads virtuales son útiles cuando la concurrencia depende de la lógica que realiza transformaciones de memoria, no de CPU, o esperando recursos externos (Socket, HttpResponses) y optimizando ese tiempo de inactividad, con una huella digital mínima en los recursos de la JVM.

Pattern Matching for switch

Java 17 introdujo la característica de “expresión switch“, entonces el switch dejó de ser solamente un “statement” en Java, sino que podía usarse como una expresión para devolver valores. Se ha perfeccionado en las versiones más recientes y en Java 21 hay una característica más limpia incluyendo muchas características que se encontraban en modo de vista previa para Java 17.

switch (obj) {
        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        -> obj.toString();
    };

Proporcionando “pattern matching” para null , mejor soporte a enumerations , sealed classes , guarded pattern para condiciones, y también el soporte a los records . Por ejemplo, en versiones previas, el “guarded pattern” usabe el operador && pero esto ha cambiado y se utiliza el operador when.

Advertisements
// As of Java 21
static void testStringNew(String response) {
    switch (response) {
        case null -> { }
        case String s when s.equalsIgnoreCase("YES") -> {
            System.out.println("You got it");
        }
        case String s when s.equalsIgnoreCase("NO") -> {
            System.out.println("Shame");
        }
        case String s -> {
            System.out.println("Sorry?");
        }
    }
}

Records Pattern

El verdadero poder de la coincidencia de patrones (pattern matching) es que se escala elegantemente para hacer coincidir gráficos de objetos más complicados, por ejemplo, con records anidados.

record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {

// As of Java 21
static void printUpperLeftColoredPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
         System.out.println(ul.c());
    }
}

Con patrones anidados podemos deconstruir dicho rectángulo con código que refleje la estructura de los constructores anidados:

// As of Java 21
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
                               var lr)) {
        System.out.println("Upper-left corner: " + x);
    }
}

Como se mencionó anteriormente, el “patrón de records” ha evolucionado junto con la “coincidencia de patrones para switch”, por lo que se ha agregado el mismo soporte a la expresión switch.

switch (obj) {
        case Rectangle(ColoredPoint ul, ColoredPoint lr) -> 
          System.out.println("The upper-left corner color is: "+ul.c());
        default        -> obj.toString();
    };

Features en modo Preview

Java ha decidido incluir características que aún no están listas, están en modo incubadora o vista previa pero están expuestas (habilitadas por un indicador de característica) para que las personas las usen y brinden comentarios. Sin embargo, es muy común que las características de versiones anteriores El modo tiene cambios en versiones futuras que no son compatibles con versiones anteriores. No se recomienda usarlos para aplicaciones listas para producción.

En este caso Java 21 incluye algunas características en modo “preview”. Por ejemplo, Structured Concurrency API , Vector API , Scoped Values , pero de mi parte me gustaría mencionar las siguientes características que podrían estar listas en futuras versiones.

String Template

Muchos lenguajes de programación ofrecen interpolación de strings como alternativa a la concatenación de strings.

C#             $"{x} plus {y} equals {x + y}"
Visual Basic   $"{x} plus {y} equals {x + y}"
Python         f"{x} plus {y} equals {x + y}"
Scala          s"$x plus $y equals ${x + y}"
Groovy         "$x plus $y equals ${x + y}"
Kotlin         "$x plus $y equals ${x + y}"
JavaScript     `${x} plus ${y} equals ${x + y}`
Ruby           "#{x} plus #{y} equals #{x + y}"
Swift          "\(x) plus \(y) equals \(x + y)"

En Java, al alternativa actual sería usar el método String.formatted.

var x = 5;
var y = 10; 
String myString = "%s plus %s equals %s".formatted(x, y, x+y);

Pero ese enfoque no es muy conveniente, en comparación con las características proporcionadas por otros lenguajes. “String Template” son un nuevo tipo de expresión en el lenguaje de programación Java. Un “String template” pueden realizar interpolación de cadenas, pero también son programables de una manera que ayuda a los desarrolladores a componer cadenas de forma segura y segura. eficientemente.

String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan");   // true

STR is a template processor defined in the Java Platform. It performs string interpolation by replacing each embedded expression in the template with the (stringified) value of that expression. The result of evaluating a template expression which uses STR is a String; e.g., "My name is Joan". Other template processor like dates, custom formats, etc, are being reviewed as part of this feature to provide a flexible API.

STR es un procesador de plantillas definido en la plataforma Java. Realiza interpolación de strings reemplazando cada expresión incrustada en la plantilla con el valor (en cadena) de esa expresión. El resultado de evaluar una expresión de plantilla que usa STR es un String; por ejemplo, "Mi nombre es Joan". Otros procesadores de plantillas, como fechas, formatos personalizados, etc., se están revisando como parte de esta función para proporcionar una API flexible.

Unnamed pattern y variables

Con las nuevas funciones de “pattern matching”, e incluso en las expresiones lambda hay una falta de variables sin nombre. ¿Qué sucede cuando se usa una expresión lambda, pero algunas de sus variables de entrada no se usan? El desarrollador debe definirlas siempre.

(x, y, z) -> x + y // z was never used, but the name was required
  
try{ ... 
} catch (NumberFormatException nfex){
   //Catch logic never use the nfex variable
}

El patrón unnamed se indica con un carácter de subrayado _, podría usarse en variables o en el patrón como comodín para indicar que no importa cuál sea el tipo del nombre. Esa característica se implementa en otros lenguajes como Scala.

(var x, var y, _) -> x + y //the third input is unnamed
  
try{ ...
} catch (NumberFormatException _){
   //Catch NumberFormatException but it does not matter the name of the variable
}

switch (obj) {
   case Rectangle(ColoredPoint ul, _) ->  //Only interested on the first parameter of rectangle
          System.out.println("The upper-left corner color is: "+ul.c());
        default        -> obj.toString();
};

Referencias

Advertisements

Leave a Reply

Your email address will not be published. Required fields are marked *