Java 14: Record, una instancia más concisa de, jpackage wrapper, switch lambdas y bloques de texto

UPD Hoy será el lanzamiento tan esperado de Java 14, e incluso si no es LTS, hay suficientes características nuevas. Java 14 estará disponible en unas pocas horas, pero puede conocerlo ahora.



En Java 14, hay suficientes cambios, tanto a nivel de escritura de código, como a nivel de API, GC y muchos otros capós del motor. Podemos decir con certeza que si conoce algunos superchips de Kotlin o Python, no se preocupe, con un alto grado de probabilidad de que pronto aparezcan en Java. En cualquier caso, el lanzamiento de hoy contiene algunos de ellos. Pero lo primero es lo primero.

En Java 14, nos esperan las siguientes innovaciones:

  • JEP 305. Asignación de una referencia a un objeto que se verifica a través de instanceof.
  • JEP 343. El packer jpackage (Incubadora).
  • JEP 345. Asignación de memoria basada en NUMA para G1.
  • JEP 349. Transmisión de eventos a través del JFR.
  • JEP 352. Buffers de byte mapeado inmutable.
  • JEP 358. Consejos sobre NullPointerException.
  • Jep 359. Registro.
  • JEP 361. Cambia las lambdas.
  • JEP 362. Los puertos Solaris y SPARC ahora están en desuso.
  • JEP 363. Eliminando el recolector de basura Concurent Mark Sweep que anteriormente estaba marcado como obsoleto.
  • JEP 364. Portar ZGC en macOS.
  • JEP 365. Portar ZGC a Windows.
  • JEP 366. La combinación de ParallelScavenge + SerialOld GC ahora está en desuso.
  • JEP 367. Eliminación de herramientas y API de Pack200 (que se marcaron como obsoletas en Java 11).
  • JEP 368. Bloques de texto.
  • JEP 370. API de acceso a memoria externa (incubadora).

Hablemos de cada mejora, algunas de las cuales serán discutidas con más detalle.

JEP 305. Asignación de una referencia a un objeto verificado a través de instanceof




Considere la situación de verificar el tipo de un objeto a través de instanceof. Si queremos personalizar explícitamente un objeto para el tipo requerido sin arriesgar una excepción ClassCastException, primero debemos asegurarnos de que el objeto sea del tipo que necesitamos.

    private static void showNameIfToy(Object o) {
        if (o instanceof Toy) {  //,    -  Toy
            Toy t = (Toy) o;  //  
            System.out.println("Toy's name: " + t.getName());  //-   
        }
    }

En este ejemplo, que ocurre, por cierto, todo el tiempo, a) nos aseguramos de tener un objeto del tipo deseado, b) le asignamos un enlace, c) hacemos algo con él, según nuestra lógica. Al parecer, en Oracle, vieron un cierto esplendor de código en esta construcción en particular, y decidieron reducirlo exactamente un paso. Ahora puedes escribir así:

    private static void showNameIfToy(Object o) {
        if (o instanceof Toy t) {  //      
            System.out.println("Toy's name: " + t.getName());  //     
        }
    }

Cómo todo esto es realmente conveniente y útil, lo dejo para juzgarlo, lector. Sí, de hecho, esta construcción en esta forma ocurre, probablemente, en el 99% de los casos asociados con la instancia de, y, tal vez, la simplificación arraigará (o quizás no). El tiempo dirá.

JEP 343. El jpackage Packer (Incubadora)




Nosotros, los desarrolladores, somos gente que recibe un disparo, no nos asustará con la instalación de un dzharnik, pero ¿qué pasa con un usuario simple que no quiere saber al instalar la JVM que está instalado en otros 3 mil millones de máquinas, no quiere saber sobre la plataforma multiplataforma de Java, pero solo quiere meter 2 veces el archivo .exe si tiene Windows, o simplemente arrastre la aplicación .app a la carpeta Aplicaciones, si tiene una amapola, ¿y no la vaporiza? ¿Cómo crear todas las condiciones para los programadores para que empaqueten sus aplicaciones en "ejecutables" familiares para el usuario final?

Parece que Oracle se dio cuenta del problema y decidió escribir un empaquetador que empacará las aplicaciones inmediatamente en un formato adecuado para la plataforma:

  • Linux: deb y rpm
  • macOS: pkg y dmg
  • Windows: msi y exe

Se ve algo como esto:

$ jpackage --name myapp --input lib --main-jar main.jar --type exe

--name myapp - nombre futuro de la aplicación
--input lib - fuente del archivo jar
--main-jar main.jar - nombre del archivo que contiene la clase principal
--type exe - tipo en el que se empaquetará la aplicación.

Después de empaquetar, puede hacer doble clic en myapp.exe (si tiene Windows) e instalarlo como una aplicación normal de Windows. La interfaz gráfica es proporcionada por JavaFX.

Intentemos construir una aplicación de este tipo utilizando la plataforma Windows.

Entonces, tomemos el proyecto desde aquí:
https://github.com/promoscow/bouncer
Al enviar una solicitud GET, recibimos el mensaje: "Rebote exitoso" y la marca de tiempo.

Poner un dzharnik. Como tenemos gradle, está en la carpeta build / libs.



Vaya a la carpeta de compilación, ingrese el mínimo requerido de comandos:

jpackage --name bouncer --input libs --main-jar bouncer.jar --type exe

Tenemos, realmente, un exe-shnik.



Nos metemos dos veces en el ejecutable, se produce la instalación habitual de la aplicación.
¡Guauu!



La aplicación ha sido instalada y está esperando en las alas.
En Archivos de programa, en la carpeta bouncer, puede ejecutar bouncer.exe.



JEP 345. Asignación de memoria asignada por NUMA para G1


Existe tal problema: NUMA, acceso no uniforme a la memoria, acceso desigual a la memoria. En otras palabras, el acceso a enchufes remotos en máquinas de múltiples secciones puede llevar un tiempo considerable, especialmente para el recolector de basura G1. En Java 14, intentaron resolver este problema de la siguiente manera: el

montón G1 está organizado como un conjunto de áreas de tamaño fijo. Una región suele ser una colección de páginas físicas, aunque cuando se usan páginas grandes (a través de -XX: + UseLargePages), varias regiones pueden formar una página física.

Si agrega el parámetro + XX: + UseNUMA durante la inicialización de JVM, las regiones se distribuirán uniformemente sobre el número total de nodos NUMA disponibles.

Oracle promete que aunque este enfoque no es completamente flexible, lo mejorarán en el futuro.

JEP 349. Transmisión de eventos a través del JFR


Existe tal cosa en Java como JFR (Java Flight Recording): grabación de eventos sobre la marcha, que le permite monitorear alrededor de 500 variedades de eventos mientras se ejecuta la aplicación. El problema es que la mayoría de ellos solo se pueden ver en los registros. La mejora que ofrece Oracle es implementar un controlador, por ejemplo, una función lambda que se llamará en respuesta a un evento.

Así es como se ve:

try (var rs = new RecordingStream()) {
  rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
  rs.onEvent("jdk.CPULoad", event -> System.out.println(event.getFloat("machineTotal")));
  rs.start();
}

Este evento muestra cada segundo la carga del procesador en la consola.



JEP 358. Consejos sobre NullPointerException


Otra conveniencia obvia, diseñada para simplificar la búsqueda de errores en el código. Imagine una construcción: un planeta, hay muchos países en el planeta, en cada país hay muchas ciudades.

public class Planet {

    private List<Country> countries;
    //...
}

public class Country {

    private List<City> cities;
    //...
}

public class City {

    private String name;
    //...
}

Decidimos mostrar los códigos hash de todas las ciudades del planeta:

planet.getCountries().forEach(c -> c.getCities().forEach(city -> city.hashCode()));

Pero no pensaron en la inicialización obligatoria de los campos. Y en algún momento obtuvieron una NullPointerException:

Exception in thread "main" java.lang.NullPointerException
	at ru.xpendence.jep_358_nullpointerexception.Main.main(Main.java:19)

¿Qué campo tenemos nulo? ¿planeta? ¿país? ciudad ??? No sabemos. Ponemos el punto de interrupción en la línea correcta y, con un suspiro, empeoramos.

En Java 14, una NullPointerException es más informativa:

Exception in thread "main" java.lang.NullPointerException: Cannot assign field "cities" because "countries" is null
     at Main.main(Main.java:21)
     ...

E inmediatamente todo está claro. países es nulo.

JEP 359. Registro




No es de extrañar que Oracle haya cambiado el ciclo de lanzamiento a uno de seis meses. Los cambios tectónicos en la industria de TI hacen que incluso tales líderes se muevan más rápido. Y si, por ejemplo, Kotlin y Python en el momento de su aparición fueron influenciados por Java (esto es lo que Wikipedia dice sobre Python, de todos modos), ahora Java está mirando a sus seguidores, por así decirlo. Acerca de Python será menor, pero la siguiente característica de Oracle se buscó con precisión en Kotlin. Se trata de clases de datos, que en Java ahora se llaman registro.

¿Qué es una clase de datos? Escribamos un POJO regular:

public class Station {

    private String name;
    private Coordinates coordinates;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Coordinates getCoordinates() {
        return coordinates;
    }

    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PlainStation that = (PlainStation) o;
        return Objects.equals(name, that.name) &&
                Objects.equals(coordinates, that.coordinates);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, coordinates);
    }

    @Override
    public String toString() {
        return "PlainStation{" +
                "name='" + name + '\'' +
                ", coordinates=" + coordinates +
                '}';
    }
}

Aquí tenemos todo: getters, setters, equals, hashcode, toString ... Para deshacerse de esta desgracia de alguna manera, a las buenas personas se les ocurrió Lombok.

En Jetbrains, el problema se resolvió de una vez de una manera más radical: inventando clases de datos para Kotlin. Tal clase con todos los métodos estándar se ve así:

data class Station(val name: String, val coordinates: Coordinates)

Y eso es. Al parecer, en Oracle, miró este diseño e hizo exactamente lo mismo, solo registró:

public record RecordStation(String name, List<Coordinates> coordinates) {}

Contiene getters, setters, equals, hashcode y toString estándar.
Ya se puede crear una clase de este tipo en IDEA 2020.1.

¿Cuáles son las diferencias con POJO?


  • El registro no se puede heredar de ninguna clase.
  • El registro no puede tener ningún otro campo del objeto, excepto los declarados en el constructor al describir la clase (por cierto, este es el constructor predeterminado). Estática: puedes.
  • Los campos son implícitamente finales. Los objetos son implícitamente finales. Con todas las consecuencias, como la imposibilidad de ser abstracto.

Resulta que esta es una clase de datos tan inmutable, no destinada a algunas acciones lógicas complejas con ella, sino destinada a la transferencia de datos, y eso es todo.

JEP 361. Cambiar Lambdas




Parece que Oracle ha tomado el interruptor con fuerza. Antes del lanzamiento actual, era un diseño bastante voluminoso, y se veía así:

    public static void translateDayOfWeekOld(String dayOfWeek) {
        switch (dayOfWeek) {
            case "MONDAY":
                System.out.println("");
                break;
            case "TUESDAY":
                System.out.println("");
                break;
            case "WEDNESDAY":
                System.out.println("");
                break;
            case "THURSDAY":
                System.out.println("");
                break;
            case "FRIDAY":
                System.out.println("");
                break;
            case "SATURDAY":
                System.out.println("");
                break;
            case "SUNDAY":
                System.out.println("");
                break;
            default:
                System.out.println("Day of week not found, try again with today day of week");
                String displayName = LocalDate.now().getDayOfWeek().name();
                translateDayOfWeek(displayName);
        }
    }

Procesar una condición requiere al menos tres líneas: caso, acción, interrupción. Ahora, con la funcionalidad mejorada del interruptor, podemos acortar la construcción anterior a esto:

    public static void translateDayOfWeek(String dayOfWeek) {
        switch (dayOfWeek) {
            case "MONDAY" -> System.out.println("");
            case "TUESDAY" -> System.out.println("");
            case "WEDNESDAY" -> System.out.println("");
            case "THURSDAY" -> System.out.println("");
            case "FRIDAY" -> System.out.println("");
            case "SATURDAY" -> System.out.println("");
            case "SUNDAY" -> System.out.println("");
            default -> {
                System.out.println("Day of week not found, try again with today day of week");
                String displayName = LocalDate.now().getDayOfWeek().name();
                translateDayOfWeek(displayName);
            }
        }
    }

De acuerdo, bastante compacto, a la moda y con lambdas. ¿Vale la pena, tú decides?

Por cierto, cambiar IDEA 2020.1, escrito de acuerdo con las viejas reglas, sugiere cuidadosamente reescribir de una nueva manera.



JEP 362. Los puertos Solaris y SPARC ahora están en desuso


Todo es simple aquí. Oracle decidió que no valía la pena gastar recursos en el soporte de los puertos Solaris y SPARC, y los empleados liberados deberían cambiar al desarrollo de nuevas funciones.

JEP 363. Eliminación del recolector de basura Concurent Mark Sweep que anteriormente estaba marcado como obsoleto


La eliminación del recolector de basura CMS se discutió hace dos años, en la novena versión. Durante este tiempo, salieron dos recolectores de basura: ZGC y Shenandoah. Al mismo tiempo, ninguno de los contribuyentes confiables de Oracle prestó atención al soporte de CMS.

En general, el médico le dijo a la morgue, luego a la morgue.

JEP 364, 365. Portar ZGC en macOS y Windows


Normalmente implementamos aplicaciones en plataformas Linux, pero para el desarrollo local a menudo utilizamos Windows y Mac. Para estas necesidades, Java 14 portó el recolector de basura ZGC a estas dos plataformas.

JEP 366. La combinación ParallelScavenge + SerialOld GC ahora está en desuso


Existe una configuración de recolector de basura extremadamente raramente utilizada, cuando el joven ParallelScavenge se combina con SerialOld. No está claro por qué se hace esto, ya que ParallelScavenge es paralelo y SerialOld, por el contrario, no lo es. La inclusión de esta combinación requiere bailes especiales con una pandereta y requiere muchos reveladores de sangre. "Oracle se preocupa por ti", por lo que marca esta configuración como obsoleta y espera enviarla pronto al recolector de basura CMS.

Alegrémonos, hermanos.

JEP 367. Eliminación de herramientas y API de Pack200 (que se marcaron como obsoletas en Java 11)


El cambio de pack200, unpack200 y API pack200 llegó para un merecido descanso. Y la razón es la obsolescencia de este empacador. Érase una vez, cuando Internet era moderno y su velocidad era de 56k (recuerdan los boomers), JDK tuvo que ser bombeado durante horas. Se inventó un empaquetador que comprimiría mejor JDK y reduciría el tiempo de descarga. Además, podrían comprimir applets y aplicaciones cliente. Pero el tiempo pasó y, a las velocidades actuales, el empacador no es relevante.

Se eliminarán los siguientes paquetes:

java.util.jar.Pack200
java.util.jar.Pack200.Packer
java.util.jar.Pack200.Unpacker

, así como el módulo jdk.pack.

JEP 368. Bloques de texto




En un momento, Java tuvo (entre una docena y media de otros idiomas) un impacto en Python. Como Python está ganando popularidad rápidamente y está presionando con confianza con otros líderes de las listas de TI de Java y C, no hay nada de malo en espiarlo. En un momento, Java 9 introdujo JShell, muy similar al pythonium Jupiter. Y ahora, ha llegado el momento de los bloques de texto.

Cuando necesitamos escribir una línea, escribimos una línea:

String s = "";

Cuando necesitamos escribir una cadena formateada, escribimos algo como esto:
String oldHtml = "<html>\n\t<body>\n\t\t<p>Hi all!</p>\n\t</body>\n</html>";

El texto es completamente ilegible. Y así, en Java 14 el problema está resuelto. Ahora, usando comillas triples, puede escribir cualquier texto:

        String html = """
                      <html>
                          <body>
                              <p>Hi all!</p>
                          </body>
                      </html>
                      """;

Es mucho más conveniente y legible. Simplemente podemos copiar cualquier texto en el código y no molestarnos con pestañas y guiones. ¡La belleza! Si solo necesitamos transferir el texto a otra línea en dicho texto sin hacer un guión, podemos usar un nuevo literal: la barra invertida. Este símbolo generaliza que la transferencia que sigue no es una transferencia. Ejemplo:

        String text = """
                    \
                 ,  , \
                     \
                   . \
                , ,  ! \
                  ; \
                    \
                   .
                """;

Conclusión:

Los dioses todavía te han dado días dorados, noches doradas y vírgenes lánguidas fijadas en tus ojos atentos. ¡Juega, canta, oh amigos! Pierde una tarde fugaz; Y tu alegría de descuido A través de las lágrimas sonrío.


JEP 370. API de acceso a memoria externa (incubadora)


Sucede que una aplicación accede a la memoria externa como Ignite, mapDB, memcached, etc. Las API existentes para acceder a ella funcionan bastante bien, pero Oracle quería algo global. Entonces aparecieron las abstracciones de MemorySegment, MemoryAddress y MemoryLayout. Mientras la función está en la incubadora, y todos pueden estar contentos con ByteBuffer, Unsafe y JNI.

Conclusión


No sé sobre usted, lector, pero me gusta el ciclo de lanzamiento de seis meses al que Oracle ha cambiado desde Java 9. Ahora Oracle no establece la tarea de lanzar lanzamientos absolutamente estables, pero el sofisticado javista no será difícil mantenerse al tanto de la estabilidad de una característica u otra, observando para su desarrollo y probar algo de la incubadora. El lenguaje se ha vuelto más vibrante, cambiante, innovaciones muy audaces y comenzaron a aparecer préstamos de otros idiomas. Sí, alguien no sigue el ritmo de los lanzamientos, pero nuestra profesión es tal que necesitamos mantenernos al día, así que si queremos o no nos encontramos con Java 14.

Como de costumbre, adjunto un proyecto en un github con ejemplos de código: [haga clic aquí]

All Articles