Java 14: Record, une instance plus concise, wrapper jpackage, switch lambdas et blocs de texte

UPD. Aujourd'hui sera la sortie tant attendue de Java 14 - et même s'il ne s'agit pas de LTS - il contient suffisamment de nouvelles fonctionnalités. Java 14 sera disponible dans quelques heures - mais vous pouvez le connaître maintenant.



Dans Java 14, il y a suffisamment de changements, à la fois au niveau de l'écriture de code et au niveau de l'API, du GC et de nombreux autres capots de moteur. Nous pouvons dire avec une certaine certitude que si vous connaissez des super-puces Kotlin ou Python - ne vous inquiétez pas, avec un haut degré de probabilité, elles apparaîtront bientôt en Java. En tout cas, la version d'aujourd'hui en contient certains. Mais tout d'abord.

En Java 14, les innovations suivantes nous attendent:

  • JEP 305. Attribution d'une rĂ©fĂ©rence Ă  un objet en cours de vĂ©rification via instanceof.
  • JEP 343. Le packer jpackage (Incubateur).
  • JEP 345. Allocation de mĂ©moire basĂ©e sur NUMA pour G1.
  • JEP 349. Streaming d'Ă©vĂ©nements via le JFR.
  • JEP 352. Tampons d'octets mappĂ©s immuables.
  • JEP 358. Conseils sur NullPointerException.
  • Jep 359. Record.
  • JEP 361. Switch lambdas.
  • JEP 362. Les ports Solaris et SPARC sont dĂ©sormais obsolètes.
  • JEP 363. Suppression du rĂ©cupĂ©rateur Concurent Mark Sweep qui Ă©tait prĂ©cĂ©demment marquĂ© comme obsolète.
  • JEP 364. Portage de ZGC sur macOS.
  • JEP 365. Portage de ZGC vers Windows.
  • JEP 366. La combinaison de ParallelScavenge + SerialOld GC est dĂ©sormais obsolète.
  • JEP 367. Suppression des outils et des API de Pack200 (qui ont Ă©tĂ© marquĂ©s comme Ă©tant obsolètes en Java 11).
  • JEP 368. Blocs de texte.
  • JEP 370. API d'accès Ă  la mĂ©moire externe (incubateur).

Parlons de chaque amélioration, dont certaines seront discutées plus en détail.

JEP 305. Affectation d'une référence à un objet vérifié via instanceof




Considérons la situation de vérification du type d'un objet via instanceof. Si nous voulons personnaliser explicitement un objet au type requis sans risquer une exception ClassCastException, nous devons d'abord nous assurer que l'objet est le type dont nous avons besoin.

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

Dans cet exemple, qui se produit, en passant, tout le temps, nous a) nous assurons que nous avons un objet du type souhaité, b) lui assignons un lien, c) faisons quelque chose avec, en fonction de notre logique. Chez Oracle, semble-t-il, ils ont vu une certaine splendeur de code dans cette construction particulière, et ont décidé de la réduire exactement d'un pas. Vous pouvez maintenant écrire comme ceci:

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

Comme tout cela est vraiment pratique et utile, je laisse à vous juger, lecteur. Oui, en effet, cette construction sous cette forme se produit, probablement, dans 99% des cas associés à instanceof, et, peut-être, la simplification prendra racine (ou peut-être pas). Le temps nous le dira.

JEP 343. L'emballeur jpackage (incubateur)




Nous, les développeurs, sommes des gars qui se font tirer dessus, vous ne nous effrayerez pas en installant un dzharnik, mais qu'en est-il d'un simple utilisateur qui ne veut pas savoir lors de l'installation de la JVM qu'il est installé sur 3 milliards de machines supplémentaires, ne veut pas connaître Java multiplateforme, mais veut juste piquer 2 fois le fichier .exe s'il a Windows, ou faites simplement glisser l'application .app vers le dossier Applications, s'il a un coquelicot, et ne le cuisez pas? Comment créer toutes les conditions pour les programmeurs afin qu'ils emballent leurs applications dans des «exécutables» familiers à l'utilisateur final?

Il semble qu'Oracle ait réalisé le problème et décidé d'écrire un packer qui conditionnera immédiatement les applications dans un format adapté à la plateforme:

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

Cela ressemble Ă  ceci:

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

--name myapp - futur nom de l'application
--input lib - source de l'archive jar
--main-jar main.jar - nom de l'archive contenant la classe principale
--type exe - type dans lequel l'application sera empaquetée.

Après l'empaquetage, vous pouvez double-cliquer sur myapp.exe (si vous avez Windows) et l'installer comme une application Windows standard. L'interface graphique est fournie par JavaFX.

Essayons de construire une telle application en utilisant la plateforme Windows.

Prenons donc le projet Ă  partir d'ici:
https://github.com/promoscow/bouncer
Lors de l'envoi d'une demande GET, nous obtenons le message: «Bounce Successfull» et horodatage.

Mettre un dzharnik. Puisque nous avons gradle, il se trouve dans le dossier build / libs.



Accédez au dossier de génération, entrez le minimum de commandes requis:

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

Nous obtenons, vraiment, un exe-shnik.



Nous poussons deux fois dans l'exécutable, l'installation habituelle de l'application se produit.
Hou la la!



L'application a été installée et attend dans les coulisses.
Dans Program Files, dans le dossier du videur, vous pouvez exécuter bouncer.exe.



JEP 345. Allocation de mémoire allouée NUMA pour G1


Il y a un tel problème - NUMA, accès mémoire non uniforme, accès mémoire inégal. En d'autres termes, l'accès aux prises distantes dans les machines à plusieurs sections peut prendre un temps considérable, en particulier pour le garbage collector G1. En Java 14, ils ont essayé de résoudre ce problème comme suit: Le

tas G1 est organisé comme un ensemble de zones de taille fixe. Une région est généralement une collection de pages physiques, bien que lorsque vous utilisez de grandes pages (via -XX: + UseLargePages), plusieurs régions peuvent constituer une page physique.

Si vous ajoutez le paramètre + XX: + UseNUMA lors de l'initialisation de la JVM, les régions seront réparties uniformément sur le nombre total de nœuds NUMA disponibles.

Oracle promet que bien que cette approche ne soit pas entièrement flexible, ils l’amélioreront à l’avenir.

JEP 349. Streaming d'événements via le JFR


Il existe une telle chose en Java comme JFR - Java Flight Recording - enregistrement d'événements à la volée, qui vous permet de surveiller environ 500 variétés d'événements pendant que l'application est en cours d'exécution. Le problème est que la plupart d'entre eux ne peuvent être consultés que dans les journaux. L'amélioration offerte par Oracle consiste à implémenter un gestionnaire, par exemple une fonction lambda qui sera appelée en réponse à un événement.

Voici à quoi ça ressemble:

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();
}

Cet événement affiche chaque seconde la charge du processeur sur la console.



JEP 358. Conseils sur NullPointerException


Une autre commodité évidente, conçue pour simplifier la recherche d'erreurs dans le code. Imaginez une construction - une planète, il y a beaucoup de pays sur la planète, dans chaque pays il y a beaucoup de villes.

public class Planet {

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

public class Country {

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

public class City {

    private String name;
    //...
}

Nous avons décidé d'afficher les codes de hachage de toutes les villes de la planète:

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

Mais ils n'ont pas pensé à l'initialisation obligatoire des champs. Et à un moment donné, ils ont obtenu une NullPointerException:

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

Quel champ avons-nous nul? planète? pays? ville ??? Nous ne savons pas. Nous plaçons le point d'arrêt sur la bonne ligne et, avec un soupir, nous avançons.

Dans Java 14, une NullPointerException est plus informative:

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

Et immédiatement tout est clair. pays est nul.

JEP 359. Record




Pas étonnant qu'Oracle ait changé le cycle de publication en un cycle de six mois. Les changements tectoniques dans l'industrie informatique accélèrent même ces leaders. Et si, par exemple, Kotlin et Python au moment de leur apparition étaient influencés par Java (c'est ce que Wikipédia dit à propos de Python, de toute façon), Java regarde maintenant ses adeptes, pour ainsi dire. À propos de Python, il sera inférieur, mais la fonctionnalité suivante d'Oracle a été précisément recherchée dans Kotlin. Il s'agit de classes de données, qui en Java sont maintenant appelées enregistrement.

Qu'est-ce qu'une classe de données? Écrivons un POJO régulier:

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 +
                '}';
    }
}

Ici, nous avons tout dans une rangée - getters, setters, equals, hashcode, toString ... Afin de se débarrasser de cette honte, de bonnes personnes ont trouvé Lombok.

Chez Jetbrains, le problème a été résolu à un moment plus radical - en inventant des classes de données pour Kotlin. Une telle classe avec toutes les méthodes standard ressemble à ceci:

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

Et c'est tout. Dans Oracle, il semble avoir regardé cette conception et fait exactement la même chose, seul enregistrement:

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

Il contient des getters, setters, equals, hashcode et toString standard.
Une classe de ce type peut déjà être créée dans IDEA 2020.1.

Quelles sont les différences avec POJO?


  • L'enregistrement ne peut ĂŞtre hĂ©ritĂ© d'aucune classe.
  • L'enregistrement ne peut avoir aucun autre champ de l'objet, Ă  l'exception de ceux dĂ©clarĂ©s dans le constructeur lors de la description de la classe (oui, c'est d'ailleurs le constructeur par dĂ©faut). Statique - vous le pouvez.
  • Les champs sont implicitement dĂ©finitifs. Les objets sont implicitement dĂ©finitifs. Avec toutes les consĂ©quences, comme l'impossibilitĂ© d'ĂŞtre abstrait.

Il s’avère qu’il s’agit d’une classe de données immuable, non destinée à certaines actions logiques complexes avec elle, mais destinée au transfert de données, et c’est tout.

JEP 361. Switch Lambdas




Il semble qu'Oracle ait pris fermement le relais. Avant la version actuelle, c'Ă©tait un design plutĂ´t volumineux, et il ressemblait Ă  ceci:

    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);
        }
    }

Le traitement d'une condition prend au moins trois lignes - cas, action, rupture. Maintenant, avec la fonctionnalité améliorée du commutateur, nous pouvons raccourcir la construction ci-dessus à ceci:

    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);
            }
        }
    }

D'accord, assez compact, à la mode et avec des lambdas. Est-ce que ça vaut le coup, vous décidez.

Soit dit en passant, le commutateur IDEA 2020.1, écrit selon les anciennes règles, suggère soigneusement de réécrire d'une nouvelle manière.



JEP 362. Les ports Solaris et SPARC sont désormais obsolètes


Ici, tout est simple. Oracle a décidé que cela ne valait pas la peine de consacrer des ressources à la prise en charge des ports Solaris et SPARC, et les employés libérés devraient passer au développement de nouvelles fonctionnalités.

JEP 363. Suppression du récupérateur Concurent Mark Sweep qui était précédemment marqué comme obsolète


La suppression du garbage collector CMS a été discutée il y a deux ans, dans la 9ème version. Pendant ce temps, deux ramasseurs de déchets sont sortis - ZGC et Shenandoah. Dans le même temps, aucun des contributeurs dignes de confiance d'Oracle n'a prêté attention au support CMS.

En général, le médecin a dit à la morgue - puis à la morgue.

JEP 364, 365. Portage de ZGC sur macOS et Windows


Nous déployons généralement des applications sur des plates-formes Linux, mais pour le développement local, nous utilisons souvent Windows et Mac. Pour ces besoins, Java 14 a porté le garbage collector ZGC sur ces deux plates-formes.

JEP 366. Combinaison ParallelScavenge + SerialOld GC maintenant obsolète


Il existe une configuration de garbage collector extrêmement rarement utilisée - lorsque le jeune ParallelScavenge est combiné avec SerialOld. La raison pour laquelle cela est fait n'est pas claire, car ParallelScavenge est parallèle, et SerialOld, au contraire, ne l'est pas. L'inclusion de cette combinaison nécessite des danses spéciales avec un tambourin et nécessite beaucoup de révélateurs de sang. «Oracle se soucie de vous», il marque donc cette configuration comme obsolète et espère l'envoyer bientôt au garbage collector du CMS.

Réjouissons-nous, frères.

JEP 367. Suppression des outils et API Pack200 (qui ont été marqués comme obsolètes en Java 11)


Le tour de pack200, unpack200 et API pack200 est venu pour un repos bien mérité. Et la raison en est l'obsolescence de ce packer. Il était une fois, quand Internet était un modem et sa vitesse était de 56k (les boomers se souviennent), JDK a dû être pompé pendant des heures. Un packer a été inventé pour mieux compresser JDK et réduire le temps de téléchargement. En outre, ils pouvaient compresser les applets et les applications clientes. Mais le temps a passé, et aux vitesses actuelles, le packer n'est pas pertinent.

Les packages suivants seront supprimés:

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

ainsi que le module jdk.pack.

JEP 368. Blocs de texte




À un moment donné, Java avait (parmi une demi-douzaine d'autres langues) un impact sur Python. Alors que Python gagne rapidement en popularité et pousse avec confiance avec les autres leaders des graphiques Java et C IT, il n'y a rien de mal à le voir. À un moment donné, Java 9 a introduit JShell, très similaire à Jupiter en pythonium. Et maintenant, le temps est venu pour les blocs de texte.

Lorsque nous devons Ă©crire une ligne, nous Ă©crivons une ligne:

String s = "";

Lorsque nous devons écrire une chaîne formatée, nous écrivons quelque chose comme ceci:
String oldHtml = "<html>\n\t<body>\n\t\t<p>Hi all!</p>\n\t</body>\n</html>";

Le texte est complètement illisible. Et donc, dans Java 14, le problème est résolu. Maintenant, en utilisant des guillemets triples, vous pouvez écrire n'importe quel texte:

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

C'est beaucoup plus pratique et lisible. Nous pouvons simplement copier n'importe quel texte dans le code et ne pas nous embêter avec des tabulations et des tirets. La beauté! Si nous avons juste besoin de transférer le texte sur une autre ligne d'un tel texte sans faire de trait d'union, nous pouvons utiliser un nouveau littéral - la barre oblique inverse. Ce symbole généralise que le transfert qui le suit n'est pas un transfert. Exemple:

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

Conclusion:

Les dieux vous ont encore donné des jours d'or, des nuits d'or, Et des vierges languissantes fixées sur vos yeux attentifs. Jouez, chantez, ô amis! Perdre une soirée éphémère; Et ta joie de l'insouciance À travers les larmes, je souris.


JEP 370. API d'accès à la mémoire externe (incubateur)


Il arrive qu'une application accède à la mémoire externe comme Ignite, mapDB, memcached, etc. Les API existantes pour y accéder fonctionnent assez bien, mais Oracle voulait quelque chose de global. Les abstractions de MemorySegment, MemoryAddress et MemoryLayout sont donc apparues. Tant que la fonctionnalité est dans l'incubateur, tout le monde peut toujours se contenter de ByteBuffer, Unsafe et JNI.

Conclusion


Je ne sais pas pour vous, lecteur, mais j'aime le cycle de publication de six mois vers lequel Oracle est passé depuis Java 9. Or, Oracle n'a pas pour mission de publier des versions absolument stables, mais le javista sophistiqué ne sera pas difficile de se tenir au courant de la stabilité d'une fonctionnalité ou d'une autre, en observant pour leur développement et tester quelque chose de l'incubateur. La langue est devenue plus vibrante, changeante, des innovations très audacieuses et des emprunts à d'autres langues ont commencé à y apparaître. Oui, quelqu'un ne suit pas le scintillement des versions, mais notre profession est telle que nous devons suivre, donc que nous le voulions ou non, nous rencontrons Java 14.

Comme d'habitude, j'attache un projet sur un github avec des exemples de code: [cliquez ici]

All Articles