Java 14: Record, eine präzisere Instanz von Jpackage Wrapper, Switch Lambdas und Textblöcken

UPD. Heute wird Java 14 mit Spannung erwartet - und auch wenn es sich nicht um LTS handelt - es gibt genug neue Funktionen. Java 14 wird in wenigen Stunden verfügbar sein - aber Sie können es jetzt kennenlernen.



In Java 14 gibt es genügend Änderungen, sowohl auf der Ebene des Schreibens von Code als auch auf der Ebene von API, GC und vielen anderen Motorhauben. Wir können mit einiger Sicherheit sagen, dass wenn Sie etwas über Kotlin- oder Python-Superchips wissen, keine Sorge, mit hoher Wahrscheinlichkeit werden sie bald in Java erscheinen. In jedem Fall enthält die heutige Version einige davon. Aber das Wichtigste zuerst.

In Java 14 erwarten uns folgende Neuerungen:

  • JEP 305. Zuweisen eines Verweises auf ein Objekt, das durch instanceof geprüft wird.
  • JEP 343. Das Packer-Paket (Inkubator).
  • JEP 345. NUMA-basierte Speicherzuordnung für G1.
  • JEP 349. Streaming von Ereignissen über die JFR.
  • JEP 352. Unveränderliche zugeordnete Bytepuffer.
  • JEP 358. Tipps zur NullPointerException.
  • Jep 359. Rekord.
  • JEP 361. Lambdas wechseln.
  • JEP 362. Die Solaris- und SPARC-Ports sind jetzt veraltet.
  • JEP 363. Entfernen des Müllsammlers Concurent Mark Sweep, der zuvor als veraltet markiert war.
  • JEP 364. Portieren von ZGC unter macOS.
  • JEP 365. Portieren von ZGC nach Windows.
  • JEP 366. Die Kombination von ParallelScavenge + SerialOld GC ist jetzt veraltet.
  • JEP 367. Entfernen von Tools und APIs aus Pack200 (die in Java 11 als veraltet markiert waren).
  • JEP 368. Textblöcke.
  • JEP 370. Externer Speicherzugriffs-API (Inkubator).

Lassen Sie uns über jede Verbesserung sprechen, von der einige ausführlicher besprochen werden.

JEP 305. Zuweisen eines Verweises auf ein Objekt, das durch instanceof geprüft wurde




Betrachten Sie die Situation, in der der Typ eines Objekts durch instanceof überprüft wird. Wenn wir ein Objekt explizit an den erforderlichen Typ anpassen möchten, ohne eine ClassCastException zu riskieren, müssen wir zunächst sicherstellen, dass das Objekt der von uns benötigte Typ ist.

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

In diesem Beispiel, das übrigens die ganze Zeit vorkommt, stellen wir a) sicher, dass wir ein Objekt des gewünschten Typs haben, b) weisen ihm einen Link zu, c) machen etwas damit, basierend auf unserer Logik. Bei Oracle haben sie anscheinend eine gewisse Pracht des Codes in dieser speziellen Konstruktion gesehen und beschlossen, ihn um genau einen Schritt zu reduzieren. Jetzt können Sie so schreiben:

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

Wie bequem und nützlich das alles ist, überlasse ich Ihnen, Leser. Ja, tatsächlich tritt diese Konstruktion in dieser Form wahrscheinlich in 99% der mit Instanz verbundenen Fälle auf, und möglicherweise wird die Vereinfachung Wurzeln schlagen (oder vielleicht auch nicht). Die Zeit wird zeigen.

JEP 343. Der jpackage Packer (Inkubator)




Wir, die Entwickler, sind Leute, die erschossen werden. Sie werden uns nicht mit der Installation eines Dzharnik erschrecken, aber was ist mit einem einfachen Benutzer, der bei der Installation der JVM nicht wissen möchte, dass sie auf weiteren 3 Milliarden Computern installiert ist, nicht über plattformübergreifendes Java Bescheid wissen möchte, sondern nur 2 anstoßen möchte mal die .exe-Datei, wenn Windows vorhanden ist, oder ziehen Sie die .app-Anwendung einfach in den Ordner "Programme", wenn sie eine Mohnblume enthält, und dämpfen Sie sie nicht? Wie können alle Bedingungen für Programmierer erstellt werden, damit sie ihre Anwendungen in „ausführbare Dateien“ packen, die dem Endbenutzer bekannt sind?

Es scheint, dass Oracle das Problem erkannt und beschlossen hat, einen Packer zu schreiben, der Anwendungen sofort in einem für die Plattform geeigneten Format verpackt:

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

Es sieht ungefähr so ​​aus:

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

--name myapp - zukünftiger Name der Anwendung
--input lib - Quelle des JAR-Archivs
--main-jar main.jar - Name des Archivs, das die Hauptklasse
enthält - Typ exe - Typ, in den die Anwendung gepackt wird.

Nach dem Packen können Sie auf myapp.exe doppelklicken (wenn Sie Windows haben) und es als reguläre Windows-Anwendung installieren. Die grafische Oberfläche wird von JavaFX bereitgestellt.

Versuchen wir, eine solche Anwendung unter Verwendung der Windows-Plattform zu erstellen.

Nehmen wir das Projekt von hier aus:
https://github.com/promoscow/bouncer
Beim Senden einer GET-Anfrage erhalten wir die Meldung: "Bounce erfolgreich" und Zeitstempel.

Einen Dzharnik setzen. Da wir gradle haben, befindet es sich im Ordner build / libs.



Gehen Sie zum Build-Ordner und geben Sie das erforderliche Minimum an Befehlen ein:

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

Wir bekommen wirklich einen Exe-Shnik.



Wir stöbern zweimal in der ausführbaren Datei, die übliche Installation der Anwendung erfolgt.
Beeindruckend!



Die Anwendung wurde installiert und wartet in den Startlöchern.
In den Programmdateien können Sie im Ordner bouncer die Datei bouncer.exe ausführen.



JEP 345. NUMA-zugewiesene Speicherzuordnung für G1


Es gibt ein solches Problem - NUMA, ungleichmäßiger Speicherzugriff, ungleicher Speicherzugriff. Mit anderen Worten, der Zugriff auf Remote-Sockets in Maschinen mit mehreren Abschnitten kann insbesondere für den G1-Garbage Collector viel Zeit in Anspruch nehmen. In Java 14 haben sie versucht, dieses Problem wie folgt zu lösen: Der

G1-Heap ist als eine Reihe von Bereichen mit fester Größe organisiert. Eine Region ist normalerweise eine Sammlung physischer Seiten. Bei Verwendung großer Seiten (über -XX: + UseLargePages) können jedoch mehrere Regionen eine physische Seite bilden.

Wenn Sie während der JVM-Initialisierung den Parameter + XX: + UseNUMA hinzufügen, werden die Regionen gleichmäßig über die Gesamtzahl der verfügbaren NUMA-Knoten verteilt.

Oracle verspricht, dass dieser Ansatz zwar nicht ganz flexibel ist, aber in Zukunft verbessert wird.

JEP 349. Streaming von Ereignissen über die JFR


In Java gibt es so etwas wie JFR - Java Flight Recording - Aufzeichnung von Ereignissen im laufenden Betrieb, mit der Sie etwa 500 verschiedene Ereignisse überwachen können, während die Anwendung ausgeführt wird. Das Problem ist, dass die meisten von ihnen nur in den Protokollen angezeigt werden können. Die von Oracle angebotene Verbesserung besteht darin, einen Handler zu implementieren, beispielsweise eine Lambda-Funktion, die als Reaktion auf ein Ereignis aufgerufen wird.

So sieht es aus:

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

Dieses Ereignis zeigt jede Sekunde die Prozessorlast für die Konsole an.



JEP 358. Tipps zur NullPointerException


Eine weitere offensichtliche Annehmlichkeit, die die Suche nach Fehlern im Code vereinfachen soll. Stellen Sie sich eine Konstruktion vor - einen Planeten, es gibt viele Länder auf dem Planeten, in jedem Land gibt es viele Städte.

public class Planet {

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

public class Country {

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

public class City {

    private String name;
    //...
}

Wir haben beschlossen, die Hash-Codes aller Städte auf dem Planeten anzuzeigen:

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

Sie haben jedoch nicht an die obligatorische Initialisierung der Felder gedacht. Und irgendwann haben sie eine NullPointerException bekommen:

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

Welches Feld haben wir null? Planet? Land? Stadt ??? Wir wissen nicht. Wir setzen den Haltepunkt auf die richtige Linie und gehen mit einem Seufzer zum Abbau.

In Java 14 ist eine NullPointerException informativer:

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

Und sofort ist alles klar. Länder ist null.

JEP 359. Aufzeichnung




Kein Wunder, dass Oracle den Veröffentlichungszyklus auf sechs Monate geändert hat. Durch tektonische Veränderungen in der IT-Branche bewegen sich selbst solche Führungskräfte schneller. Und wenn zum Beispiel Kotlin und Python zum Zeitpunkt ihres Auftretens von Java beeinflusst wurden (das sagt Wikipedia jedenfalls über Python), schaut Java jetzt sozusagen auf seine Anhänger. Über Python wird es niedriger sein, aber die folgende Funktion von Oracle wurde in Kotlin genau betrachtet. Es geht um Datenklassen, die in Java jetzt als Datensatz bezeichnet werden.

Was ist eine Datenklasse? Schreiben wir ein reguläres POJO:

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

Hier haben wir alles - Getter, Setter, Equals, Hashcode, toString ... Um diese Schande irgendwie loszuwerden, haben sich gute Leute Lombok ausgedacht.

Bei Jetbrains wurde das Problem einmal radikaler gelöst - durch die Erfindung von Datenklassen für Kotlin. Eine solche Klasse mit allen Standardmethoden sieht folgendermaßen aus:

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

Und alle. In Oracle, so scheint es, hat sich dieses Design angesehen und genau das Gleiche getan, nur aufgezeichnet:

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

Es enthält Standard-Getter, Setter, Equals, Hashcode und toString.
Eine Klasse dieses Typs kann bereits in IDEA 2020.1 erstellt werden.

Was sind die Unterschiede zu POJO?


  • Datensatz kann von keiner Klasse geerbt werden.
  • Der Datensatz kann keine anderen Felder des Objekts enthalten, außer denen, die bei der Beschreibung der Klasse im Konstruktor deklariert wurden (ja, dies ist übrigens der Standardkonstruktor). Statisch - das können Sie.
  • Felder sind implizit endgültig. Objekte sind implizit endgültig. Mit all den Konsequenzen, wie der Unmöglichkeit, abstrakt zu sein.

Es stellt sich heraus, dass dies eine unveränderliche Datenklasse ist, die nicht für einige komplexe logische Aktionen gedacht ist, sondern für die Datenübertragung, und das ist alles.

JEP 361. Lambdas wechseln




Es scheint, dass Oracle den Wechsel fest in Angriff genommen hat. Vor der aktuellen Version war es ein ziemlich sperriges Design, und es sah folgendermaßen aus:

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

Die Verarbeitung einer Bedingung dauert mindestens drei Zeilen - Fall, Aktion, Unterbrechung. Mit der erweiterten Funktionalität des Schalters können wir die obige Konstruktion auf diese verkürzen:

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

Stimmen Sie zu, ziemlich kompakt, modisch und mit Lambdas. Lohnt es sich, entscheiden Sie.

Übrigens schlägt Switch IDEA 2020.1, geschrieben nach den alten Regeln, sorgfältig vor, das Schreiben auf eine neue Art und Weise neu zu schreiben.



JEP 362. Solaris- und SPARC-Ports jetzt veraltet


Hier ist alles einfach. Oracle entschied, dass es sich nicht lohnt, Ressourcen für die Unterstützung von Solaris- und SPARC-Ports aufzuwenden, und befreite Mitarbeiter sollten auf die Entwicklung neuer Funktionen umsteigen.

JEP 363. Entfernen des Müllsammlers Concurent Mark Sweep, der zuvor als veraltet markiert war


Die Entfernung des CMS-Garbage Collectors wurde vor zwei Jahren in der 9. Version diskutiert. Während dieser Zeit kamen zwei Müllsammler heraus - ZGC und Shenandoah. Gleichzeitig achtete keiner der vertrauenswürdigen Oracle-Mitarbeiter auf die CMS-Unterstützung.

Im Allgemeinen sagte der Arzt zum Leichenschauhaus - dann zum Leichenschauhaus.

JEP 364, 365. Portierung von ZGC unter MacOS und Windows


Wir stellen normalerweise Anwendungen auf Linux-Plattformen bereit, verwenden jedoch für die lokale Entwicklung häufig Windows und Mac. Für diese Anforderungen hat Java 14 den ZGC-Garbage Collector auf diese beiden Plattformen portiert.

JEP 366. ParallelScavenge + SerialOld GC-Kombination jetzt veraltet


Es gibt eine äußerst selten verwendete Garbage Collector-Konfiguration - wenn der junge ParallelScavenge mit SerialOld kombiniert wird. Warum dies geschieht, ist nicht klar, da ParallelScavenge parallel ist und SerialOld im Gegenteil nicht. Die Aufnahme dieser Kombination erfordert spezielle Tänze mit einem Tamburin und erfordert viele Blutentwickler. "Oracle kümmert sich um Sie", markiert diese Konfiguration als veraltet und hofft, sie bald an den CMS-Garbage Collector senden zu können.

Freut uns, Brüder.

JEP 367. Entfernen von Pack200-Tools und -APIs (die in Java 11 als veraltet markiert wurden)


Die Wende von pack200, unpack200 und API pack200 kam für eine wohlverdiente Pause. Und der Grund ist die Veralterung dieses Packers. Es war einmal, als das Internet ein Modem war und seine Geschwindigkeit 56.000 betrug (Boomer erinnern sich), musste JDK stundenlang abgepumpt werden. Es wurde ein Packer erfunden, der JDK besser komprimiert und die Downloadzeit verkürzt. Sie können auch Applets und Clientanwendungen komprimieren. Aber die Zeit verging und bei den aktuellen Geschwindigkeiten ist der Packer nicht relevant.

Die folgenden Pakete werden entfernt:

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

sowie das Modul jdk.pack.

JEP 368. Textblöcke




Zu einer Zeit hatte Java (unter anderthalb Dutzend anderen Sprachen) einen Einfluss auf Python. Da Python immer beliebter wird und selbstbewusst mit anderen führenden Unternehmen der Java- und C-IT-Charts zusammenarbeitet, ist es nichts Falsches, darauf zu schauen. Zu einer Zeit führte Java 9 JShell ein, das Pythonium Jupiter sehr ähnlich ist. Und jetzt ist die Zeit für Textblöcke gekommen.

Wenn wir eine Zeile schreiben müssen, schreiben wir eine Zeile:

String s = "";

Wenn wir einen formatierten String schreiben müssen, schreiben wir ungefähr so:
String oldHtml = "<html>\n\t<body>\n\t\t<p>Hi all!</p>\n\t</body>\n</html>";

Der Text ist völlig unlesbar. In Java 14 ist das Problem also gelöst. Mit dreifachen Anführungszeichen können Sie jetzt jeden Text schreiben:

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

Es ist viel bequemer und lesbarer. Wir können einfach jeden Text in den Code kopieren und uns nicht um Tabulatoren und Bindestriche kümmern. Die Schönheit! Wenn wir den Text nur in eine andere Zeile in einem solchen Text übertragen müssen, ohne einen Bindestrich zu erstellen, können wir ein neues Literal verwenden - den Backslash. Dieses Symbol verallgemeinert, dass die darauf folgende Übertragung keine Übertragung ist. Beispiel:

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

Fazit:

Die Götter haben dir immer noch goldene Tage, goldene Nächte und träge Jungfrauen gegeben, die auf deine aufmerksamen Augen gerichtet sind. Spiel, singe, o Freunde! Verliere einen flüchtigen Abend; Und deine Freude an Nachlässigkeit Durch Tränen lächle ich.


JEP 370. Externer Speicherzugriffs-API (Inkubator)


Es kommt vor, dass eine Anwendung auf externen Speicher wie Ignite, mapDB, memcached usw. zugreift. Die vorhandenen APIs für den Zugriff darauf funktionieren recht gut, aber Oracle wollte etwas Globales. So erschienen die Abstraktionen von MemorySegment, MemoryAddress und MemoryLayout. Während sich die Funktion im Inkubator befindet und jeder weiterhin mit ByteBuffer, Unsafe und JNI zufrieden sein kann.

Fazit


Ich weiß nichts über Sie, Leser, aber ich mag den sechsmonatigen Release-Zyklus, auf den Oracle seit Java 9 umgestellt hat. Jetzt stellt Oracle nicht die Aufgabe, absolut stabile Releases zu veröffentlichen, aber es wird nicht schwierig sein, mit der Stabilität der einen oder anderen Funktion Schritt zu halten für ihre Entwicklung und Prüfung etwas aus dem Inkubator. Die Sprache ist lebendiger, veränderlicher geworden, sehr kühne Innovationen und Anleihen aus anderen Sprachen tauchten in ihr auf. Ja, jemand hält nicht mit dem Flackern von Veröffentlichungen Schritt, aber unser Beruf ist so, dass wir Schritt halten müssen. Ob wir es wollen oder nicht, wir treffen Java 14.

Wie üblich füge ich ein Projekt auf einem Github mit Codebeispielen hinzu: [hier klicken]

All Articles