Dado que muchos desarrolladores aprendieron Java hace años, te mostramos algunas de las características, introducidas en los últimos años, que pueden ayudarlo a modernizar y mejorar su código. Piense en estas mejoras de Java como una fruta sabrosa que está a su alcance: si no está utilizando estas 11 funciones en su software, debería hacerlo. Como mínimo, debes probarlos.
No son todas características nuevas . De hecho, este artículo se centra en algunas funciones antiguas que es posible que usted y su equipo de desarrolladores no estén utilizando.
La conclusión principal es que los cambios de gran alcance siempre han sido una realidad para los desarrolladores de Java (como en muchos lenguajes de programación), y el ejercicio de mantenerse al tanto de estos cambios puede ayudar a mantener su código flexible. Si no está utilizando estas 11 mejoras de idioma, tal vez sea el momento de comenzar.
Paquete de fecha / hora (introducido con Java 8)
Uno de mis beneficios favoritos, incluido con Java 8, es el
Muchos de los primeros en adoptarlo lucharon con la
Las principales clases de la mayoría de los desarrolladores trabajan son
Por ejemplo, para usar la fecha de hoy, simplemente haga referencia
LocalDate d = LocalDate.now();
System.out.println("Today is " + d);
Today is 2027-03-12
Puede imprimir fechas y fechas y horas en cualquier formato que desee utilizando un
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy/LL/dd");
System.out.println(df.format(LocalDate.now()));
2027/03/12
Debido a que no todo el código se puede convertir a la nueva API de fecha / hora de la noche a la mañana, puede convertir entre la API anterior y la nueva utilizando métodos integrados en ambas.
Date oldDate = new Date();
System.out.println(oldDate.toInstant());
LocalDateTime newDate = LocalDateTime.ofInstant(oldDate.toInstant(), ZoneId.systemDefault());
System.out.println(newDate);
Hacer eso imprime lo siguiente:
2027-05-12T19:23:19.695Z
2027-05-12T15:23:19.695
Más interesante es la funcionalidad de cálculo incorporada en la nueva API. Por ejemplo, al administrar una pequeña empresa, debe saber cuándo pagar a las personas. Suponga que hay una combinación de empleados asalariados mensuales y empleados por hora pagados semanalmente. ¿Cuándo se les pagará a los trabajadores de cada tipo? El
LocalDateTime now = LocalDateTime.now();
LocalDateTime weeklyPayDay =
now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println("Weekly employees' payday is Friday " +
weeklyPayDay.getMonth() + " " +
weeklyPayDay.getDayOfMonth());
LocalDateTime monthlyPayDay = now.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
System.out.println("Monthly employees are paid on " + monthlyPayDay.toLocalDate());
El código anterior imprime lo siguiente:
Weekly employees' payday is Friday MAY 14
Monthly employees are paid on 2027-05-28
Con la ayuda de la gran variedad de métodos para crear una fecha y hora modificadas, todos los objetos de la nueva API son inmutables. La inmutabilidad es útil para escribir código seguro para subprocesos. Existe una tendencia hacia los objetos inmutables, como se verá en la sección posterior sobre registros.
Hay más en esta API. Estos ejemplos (con importaciones) están en mi javasrc en GitHub en la carpeta
Lambdas (introducido con Java 8)
Lambdas son funciones anónimas y le dieron a Java el principio de programación funcional de “código como datos”. En cierto sentido, esa capacidad siempre había estado ahí: se podía definir una variable de un tipo de objeto dado y pasarla a un método. Con lambdas, sin embargo, el proceso es mucho más sencillo y claro.
Las lambdas no son nuevas. El término fue utilizado por primera vez por Alonzo Church (de la fama de Church y Turing) en matemáticas en la década de 1930 . Para ver cómo las lambdas son implementadas por Java, consulte el artículo de Ben Evans “ Detrás de escena: ¿Cómo funcionan realmente las expresiones lambda? “
¿Cómo usas las lambdas? Considere el caso una vez común de agregar un
JButton qb = new JButton("Quit");
class QuitListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
};
ActionListener al = new QuitListener();
qb.addActionListener(al);
El código anterior crea una clase que se usa solo una vez. Puede acortar el código utilizando una clase interna anónima, de la siguiente manera:
JButton qb = new JButton("Quit");
ActionListener al = new ActionListener {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
};
qb.addActionListener(al);
Este código permite al compilador elegir un nombre para la clase, pero aún crea una única instancia que se usa solo una vez. Puede acortar un poco más el código si no obtiene una referencia a la clase interna anónima.
qb.addActionListener(new ActionListener {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
};
Pero el código sigue repitiendo mucha información que el compilador puede inferir sin ambigüedades. Las lambdas aprovechan al máximo la inferencia del compilador.
qb.addActionListener(e -> System.exit(0));
¡Auge! Se guardó tanta escritura redundante, mucho menos código para revisar y mucho menos código para leer y releer al mantener el código.
La sintaxis de lambdas es
El
Para evitar que tenga que crear sus propias interfaces funcionales, puede encontrar una serie de interfaces predefinidas en el
$ javap java.util.function.Consumer
Compiled from "Consumer.java"
public interface java.util.function.Consumer<T> {
public abstract void accept(T);
public default java.util.function.Consumer<T> andThen(java.util.function.Consumer<? super T>);
}
$
Hay un ejemplo de uso
Métodos predeterminados y List.forEach (introducido con Java 8)
Java 8 agregó la noción de métodos predeterminados a las interfaces con, como su nombre lo indica, cuerpos de métodos predeterminados definidos en la interfaz. Esto permitió cambios compatibles con versiones anteriores. Mi ejemplo favorito de eso es
public interface java.lang.Iterable<T> {
public abstract java.util.Iterator<T> iterator();
public default void forEach(java.util.function.Consumer<? super T>);
public default java.util.Spliterator<T> spliterator();
}
¿Y eso qué
Si bien el código Java antiguo no tenía el
List<String> names = List.of("Robin", "Toni", "JJ");
names.forEach(System.out::println);
La última pieza del código anterior
La
Streams (introducido con Java 8)
Una secuencia es un objeto (definido en
Considere el problema de contar el número de líneas únicas en un archivo. Sin flujos, puede leer las líneas en una matriz, ordenar la matriz y recorrerla comparando cada línea con la anterior. Otros enfoques son utilizar una tabla o un árbol de hashing. Las transmisiones son más simples y el código es más legible. A continuación, el
long numberLines = Files.lines(Path.of(("lines.txt")))
.sorted()
.distinct()
.count();
System.out.printf("lines.txt contains " + numberLines + " unique lines.");
He aquí otro ejemplo. Un método útil
La
System.out.println(new Random().doubles().limit(10).average().getAsDouble());
0.5384002737224926
System.out.println(new Random().doubles().limit(10).average().getAsDouble());
0.633140919801152
System.out.println(new Random().doubles().limit(100).average().getAsDouble());
0.514099594406035
System.out.println(new Random().doubles().limit(1000).average().getAsDouble());
0.48159324772449064
System.out.println(new Random().doubles().limit(10000).average().getAsDouble());
0.5059246586725906
Como puede ver, con valores mayores de
Módulos (introducidos con Java 9)
Hace mucho tiempo, los responsables del JDK se dieron cuenta de que su código fuente había crecido demasiado para administrarlo. Se puso en marcha un plan para modularizar el JDK, tanto para facilitar el mantenimiento como para proporcionar un mejor aislamiento entre las partes del JDK y entre el JDK y las aplicaciones, un proceso que continúa incluso hoy, como explica Ben Evans en “ Un vistazo a Java 17: Continuación de la unidad para encapsular los componentes internos del tiempo de ejecución de Java “.
Esta iniciativa, originalmente llamada Java Platform Module System, logró simplificar y segregar el código del JDK. Se decidió hacer que este mismo mecanismo fuera parte del desarrollo normal de aplicaciones Java. Si bien esto causó bastante abandono ya que las herramientas y bibliotecas de terceros se movieron más lentamente que otras para admitir la modularidad, ahora está bastante sólidamente establecido.
Lo mejor de todo es que el sistema de módulos es clave para ayudarlo a crear y mantener sus propias aplicaciones grandes.
El sistema de módulos es importante para los desarrolladores. Proporciona, por ejemplo, una declaración clara de qué partes de su código son API públicas, cuáles son implementación y cuáles son (posiblemente múltiples) implementaciones de una interfaz pública. Debería migrar a código modular si aún no lo ha hecho.
Si aún no está utilizando módulos, consulte Java 9 Modularity de Sander Mak y Paul Bakker .
JShell (introducido con Java 9)
Uno de los obstáculos que muchas personas tienen mientras aprenden Java es tener que escribir la clase pública
JShell es la respuesta a esta excesiva complejidad. JShell es un shell interactivo, también conocido como REPL (bucle de lectura-evaluación-impresión), donde la sintaxis de Java es un poco relajada y no tienes que poner
El argumento
$ jshell PRINTING
| Welcome to JShell -- Version 16
| For an introduction type: /help intro
jshell> println(2+2)
4
jshell> 2+2
$23 ==> 4
jshell> import java.time.*;
jshell> var d = LocalDate.of(2027,12,5);
d ==> 2027-12-05
jshell> /exit
| Goodbye
$
Esta herramienta no es solo para principiantes, por supuesto: uso mucho JShell cuando exploro API. Como siempre, hay más en el tema. Pruebe
Ejecución de un solo archivo (introducido con Java 11)
Java 11 le permite ejecutar un programa Java autónomo sin compilarlo primero. Por ejemplo
$ java HelloWorld.java
Hello, world
$
Debe agregar la extensión
Bloques de texto (introducidos con Java 13)
Considere el siguiente código:
String paraV1 =
"Creating long strings over multiple lines is tedious and error-prone.\n" +
"The developer could forget the '+' at the end of a line, or more\n" +
"commonly forget the space or the '\\n' at the end of a line.";
Los bloques de texto, también conocidos como cadenas de varias líneas, facilitan esta tarea. Con bloques de texto (previsualizados en Java 13 pero ahora estándar), el ejemplo anterior se convertiría en el siguiente:
String paraV2 = """
Creating long strings over multiple lines is tedious and error-prone.
The developer could forget the '+' at the end of a line, or more
commonly, forget the space or the '\\n' at the end of a line.""";
El bloque de texto requiere un carácter de nueva línea después de la cita triple de apertura inicial, descarta la sangría inicial y procesa las nuevas líneas sin problemas.
En este ejemplo,
Los bloques de texto son un gran ahorro de tiempo y de escritura cuando estás escribiendo mensajes útiles para el usuario o cualquier otra cadena larga. Obtenga más información en el artículo de Mala Gupta ” Los bloques de texto llegan a Java “.
Registros (introducido con Java 14)
Los desarrolladores de Java han dedicado innumerables horas a escribir y ajustar lo que son esencialmente clases de datos de objetos Java antiguos (POJO). Ya conoce el ejercicio: cree una clase, agregue algunos campos, agregue un constructor para asegurarse de que los campos estén inicializados, genere getters y setters, genere
Los registros automatizan este proceso. Una
public record Person(String name, String email) { }
La sintaxis parece un poco extraña al principio y parece una fusión entre una definición de clase y una definición de método. Eso es porque eso es básicamente lo que es. Pasas los argumentos para el constructor y todos se convierten en argumentos, campos y descriptores de acceso del constructor. El compilador generará todas las demás piezas necesarias.
Esto es lo que se genera realmente como se ve por el
$ javap 'structure.RecordDemoPerson$Person'
Compiled from "RecordDemoPerson.java"
public final class structure.RecordDemoPerson$Person extends java.lang.Record {
public structure.RecordDemoPerson$Person(java.lang.String, java.lang.String);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public java.lang.String name();
public java.lang.String email();
}
$ javap java.lang.Record
Compiled from "Record.java"
public abstract class java.lang.Record {
protected java.lang.Record();
public abstract boolean equals(java.lang.Object);
public abstract int hashCode();
public abstract java.lang.String toString();
}
$
En la siguiente demostración simple, los argumentos del constructor están en el mismo orden que en la definición del registro. Los métodos getter no siguen el
public static void main(String[] args) {
Person p = new Person("Covington Roderick Smythe III", "roddy3@smythe.tld");
System.out.println(p);
System.out.println(p.name);
System.out.println(p.name());
}
La ejecución de ese código muestra lo siguiente:
$ java RecordDemoPerson.java
Person[name=Covington Roderick Smythe III, email=roddy3@smythe.tld]
Covington Roderick Smythe III
Covington Roderick Smythe III
$
La demostración recupera el nombre como campo y como método para mostrar que puede usar cualquiera, por lo que el nombre se imprime dos veces en la salida.
Puede agregar métodos adicionales, pero todos los campos son inmutables. Una buena pista es que no se proporcionan métodos de establecimiento. Si necesita cambiar un registro, simplemente cree uno nuevo; son muy ligeros.
Para obtener más información sobre los registros, consulte “Los registros llegan a Java ” de Ben Evans. La inmutabilidad y la falta de nombres de métodos setter y getter pueden ser un problema con algunos frameworks; consulte “ Buceo en los registros de Java: serialización, cálculo de referencias y validación del estado del bean ” de Frank Kiwy para obtener ideas sobre cómo hacer frente a eso.
Las clases de discos son breves, sencillas y dulces. Deberías usarlos; le ahorrarán mucho escribir y reducirán significativamente la cantidad de código que tiene que leer (y mantener).
jpackage (introducido con Java 14)
En cada plataforma,
Dada la variedad de funciones que la gente espera de un instalador, no es sorprendente que haya muchas opciones de línea de comandos para
jpackage \
--name PDFShow \
--app-version ${RELEASE_VERSION} \
--license-file LICENSE.txt \
--vendor "${COMPANY_NAME}" \
--type "${inst_format}" \
--icon src/main/resources/images/logo.${icon_format} \
--input target \
--main-jar pdfshow-${RELEASE_VERSION}-jar-with-dependencies.jar \
${OS_SPECIFIC}
Los instaladores resultantes se han utilizado para instalar
Tipos sellados (introducido con Java 15)
“La subclasificación ilimitada es la raíz de todos los males”, dijo alguien una vez en una discusión sobre la arquitectura del software. Los tipos sellados, previsualizados en Java 15 y 16, proporcionan al autor de una clase control sobre sus subclases. Por ejemplo, en el siguiente
public abstract sealed class Person
permits Customer, SalesRep, Manager {...}
solo las tres clases nombradas (en el mismo paquete) pueden realizar subclases directamente desde
sealed class A permits B {
...
}
non-sealed class B extends A {
...
}
final class C extends B { // "extends A" here would not compile!
...
}
FUENTE: https://blogs.oracle.com/javamagazine/java-modernization-streams-records-lambdas-sealedclasses