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.
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
- https://blog.adamgamboa.dev/from-java-9-to-java-14-whats-new-part-2/
- https://www.baeldung.com/java-15-new
- https://www.baeldung.com/java-16-new-features
- https://blog.adamgamboa.dev/switch-expression-on-java-17/
- https://blog.adamgamboa.dev/understanding-sealed-classes-in-java-17/