Java 17 features – Nuevos desde Java 11

Advertisements

Con la nueva cadencia de lanzamientos de Java, vemos una nueva versión de Java cada 6 meses, sin embargo, debemos prestar atención a las versiones LTS. En septiembre de 2021, se lanzó la última versión LTS de Java, Java 17. Para aplicaciones maduras o listas para producción, siempre se recomienda usar la última versión estable de LTS, algunas personas pueden estar usando Java 11 (versión LTS anterior) y en muy pronto podría comenzar a pensar en actualizar a Java 17.

Por eso es muy importante tener un resumen de las características que están disponibles cuando actualizamos de Java 11 a Java 17, y este artículo se centrará en eso. La mayoría de esas funciones se introdujeron en versiones anteriores de Java (12, 13, 14, 15 o 16) como funciones de incubadora o en vista previa.

Nuevos Features

Sealed Clases (Clases Selladas)

El objetivo de las clases o interfaces selladas es proporcionar una forma de permitir que una clase sea ampliamente accesible pero no ampliamente extensible. Estos pueden ser ampliados o implementados solo por aquellas clases e interfaces que están explícitamente permitidas.

Una clase o interfaz es sellada aplicando el modificador sealed a su definición, después de cualquier extends o implements, se debe agregar la cláusula de permits seguida de las clases que permitimos que se extiendan.

public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape { ... }

Luego, en las implementaciones (clases permitidas) se debe extender la clase sealed (sellada).

public final class Circle extends Shape { ... }
public final class Rectangule extends Shape { ... }
public final class Square extends Shape { ... }
public final class WeirdShape extends Shape { ... }

Las clases selladas también se pueden aplicar a interfaces y registros, y deben seguir algunas reglas al extender la clase sellada. Para más información al respecto, recomiendo leer este artículo Entendiendo las clases selladas.

Pattern Matching para el Instance Of

¡¡¡Cambio muy simple pero útil!!! No necesitaremos convertir el objeto después de una declaración instanceof para poder usarlo de manera segura.

//Java 11
if (obj instanceof User) {
  User user = (User) obj;
  System.out.println(user.getName());
}
//Java 17
if (obj instanceof User user) {
  System.out.println(user.getName());
}

Expresión Switch

Este es el primer paso hacia un estilo de programación más declarativo y null safe, que permite una mejor manera de expresar y modelar datos, reconociendo la semántica del modelo de datos a través de patrones. Definitivamente mejorando la legibilidad de nuestro código, evitando el anidamiento de if/elseif/else en muchas circunstancias y brindando características que vemos en otros lenguajes de programación.

Usemos el siguiente código como referencia, tenemos varios if/else y el instanceof anidados.

Advertisements
static String formatter(Object o) {
    String formatted = "unknown";
    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) {
        if(s.length > 3){
          formatted = String.format("Short String %s", s);
        }  else {
          formatted = String.format("Large String %s", s);
        }
    }
    return formatted;
}

El código anterior puede ser simplificado usando las expresiones switch the la siguiente manera.

static String formatterPatternSwitch(Object o) {
    return 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 && (s.length > 3) -> String.format("Short String %s", s);
        case String s && (s.length > 10 -> String.format("Large String %s", s);
        default        -> o.toString();
    };
}

Para más información y detalles sobre la expresión switch recomiendo ver este artículo

Bloques de Texto

Nos permite crear cadenas multilínea fácilmente. La cadena de varias líneas debe escribirse dentro de un par de comillas dobles triples.

String sqlSentence = """
					select * from Item i
                    where i.price > 5000
  					and i.saleDate = '01/01/2020'
        			""";

String textBlockJSON = """
		{
			"name":"Pankaj",
			"website":"JournalDev"
		}""";

Records

Esta es una de mis características nuevas favoritas agregadas en Java. Reduce mucho nuestro código, y estoy seguro de que será una buena opción para el famoso DTO (objeto de transferencia de datos). Un registro compacta la sintaxis para declarar una clase que almacena datos inmutables puros y no tiene lógica. Es similar a Data Class en Kotlin o Case Class en Scala. Los registros evitan agregar código “boilerplate” porque los métodos constructor, getters/setters,  equals,  hashCode  y  toString se generan automáticamente.

public record Person (String name, String lastName, int age){}

Si queremos hacer lo mismo con una clase Java regular, es posible que debamos generar algo como el siguiente código.

public class Person {
  private String name;
  private String lastName;
  private int age;
  
  public String getName(){
    return name;
  }
  public void setName(String name){
    this.name = name;
  }
  
  public String getLastName(){/*Code*/}
  public void setLastName(String lastName){/*Code*/}
  public int getAge(){/*Code*/}
  public void setAge(int age){/*Code*/}
  
  public boolean equals(Object obj){/*Code*/}
  public int hashCode(){/*Code*/}
  public String toString(){/*Code*/}
}

Por supuesto, los POJO clásicos (Plain Old Java Object) siguen siendo válidos, pero ahora tenemos otra estructura para usar en nuestro código de acuerdo con nuestros requisitos. Por eso es importante tener en cuenta las siguientes características de un record.

  • Los campos declarados son privados y finales.
  • Los accesores o los métodos auto-generados se pueden redefinir.
  • Es posible crear nuevos métodos en un record.
  • Los records no permiten crear campos de instancia en su cuerpo.
  • Los records permiten crear campos estáticos en su cuerpo.
  • Los records permiten múltiples constructores.
  • Los records pueden implementar interfaces
  • Los records no pueden extenderse a otras clases.
  • Los records no pueden ser abstractos.

Método Stream.toList

El objetivo es reducir el repetitivo con algunos recopiladores de secuencias de uso común, como Collectors.toList y Collectors.toSet

List<String> integersAsString = Arrays.asList("1", "2", "3");
List<Integer> intsEquivalent = integersAsString.stream()
  .map(Integer::parseInt)
  .toList();

En lugar de usar uno de los métodos en la clase Collectors

List<Integer> ints = integersAsString.stream()
  .map(Integer::parseInt)
  .collect(Collectors.toList());

Conclusiones

Como podemos ver, Java como lenguaje de programación está evolucionando constantemente agregando nuevas funciones para simplificar las tareas del desarrollador, reduciendo el código, agregando nuevos patrones y adaptando el lenguaje a las nuevas tendencias y características que ofrecen otros lenguajes. Sin duda, la actualización de Java 11 a Java 17 hará que algunas funciones interesantes estén disponibles.

Referencias

Advertisements

Leave a Reply

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