Welche Neuerungen gibt es in Java 13?

Click here to read this article in English

Dadurch, dass sich der Releasezyklus von Java geändert hat wird nun jedes halbe Jahr eine neue Java Version veröffentlicht. So erscheint im September 2019 Java 13. Java Entwickler müssen sich dadurch aber nicht stressen lassen: Immerhin verwenden immer noch viele Firmen Java 8 *hust hust* uhm ich meine Java 11 und die nächste LTS = Long Term Support-Version wird mit Java 17 im 2021 erscheinen.

Zwar kommen jetzt pro Jahr zwei Versionen auf den Markt, dafür sind sie meistens auch kleiner und überschaubarer. Dazu kommt, dass einige Änderungen erst einmal in einer Vorschau-Version herausgegeben wird, um das Feedback der Community einzuholen.

Genug geredet, was sind denn jetzt nun die Neuerungen?

Folgende sogenannte JPE’s = Java Enhancement Proposals werden in Java 13 erscheinen:

  • Switch Expressions (Vorschau)
  • Text Blocks (Vorschau)
  • Z Garbage Collector: Uncommit Unused Memory
  • Dynamic CDS Archives
  • Reimplementation of the Legacy Socket API

Für den Entwickler sind vorallem die ersten beiden Punkte interessant, weshalb ich diesen ein eigenes Kapitel widmen werde.

Bei ZGC = Z Gargabe Collector handelt es sich um eine neuen neuen… ähm, ja nun um einen Garbage Collector halt, wie der Name sagt 🙂 Dieser verspricht, auch bei Terabyte grossem Heap diesen schnell aufzuräumen und auch noch freizugeben. Das Freigeben war anscheinend bisher nicht der Fall, weshalb einige Anwendungen über eine längere Laufzeit mehr Speicher als nötig verwendet hatten.

Bei Dynamics CDS Archives handelt es sich um eine Erweiterung von den in Java 5 eingeführten CDS = Class Data Sharing. Bei diesem werden bestimte Informationen über Applikationen in CDS-Archiven abgelegt, um die Startzeit der Applikationen zu verkürzen. Dies war bis Java 10 aber dem Bootstrap Class Loader vorenthalten. Mit Java 10 wurde der AppCDS = Application Class Data Sharing eingeführt, mit dem auch andere Classloader auf diese Archive zugreifen können. Damit die CDS Archive angelegt werden konnten, mussten Probeläufe gemacht werden, um festzustellen, welche Klassen effektiv geladen werden sollten – Diese wurden dann in Klassenlisten gespeichert. Mit Dynamic CDS sollen nun diese Probeläufe wegfallen.

Und dann gibt es noch eine neue Implementation vom Socket API, welche noch aus dem JDK 1 stammen. NioSocketImplementation löst die veraltete PlainSocketImplementation. Der Name ist dabei abgeleitet der New I/O-Implementierung.
Mehr Infos dazu gibt es etwa unter https://docs.oracle.com/javase/8/docs/technotes/guides/io/index.html

Wie aktiviere ich Vorschau-Erweiterungen?

Vorschau bedeutet, dass es in Zukunft noch Änderungen an diesen Neuerungen geben kann. Damit diese dennoch mit Java 13 geladen werden, muss beim Kompilieren ein Flag mitgegeben werden:

javac --release 13 --enable-preview MyClass.java

Auch beim Starten der Applikation muss das Flag angegeben werden:

java --enable-preview MyClass

Wenn man Maven verwenden will, muss das Flag im pom.xml beim maven-compiler-plugin angegeben werden.

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>3.8.0</version>
	<configuration>
		<release>13</release>
		<compilerArgs>--enable-preview</compilerArgs>
	</configuration>
</plugin>

Was sind Switch Expressions?

Switch Expressions erlauben einem im Unterschied zu einem Switch-Case direkt Werte zurückzugeben (zurückgeben im Sinne von „return“).

Dabei muss das neue Schlüsselwort yield statt wie bisher break verwendet werden.

Switch-Case Beispiel:

String name = "";
 switch(personalId) {
     case 1:
     case 2:
         name = "Fritz";
         break;
     case 3:
         name = "Hanswurst";
         break;
 };
 return name;

Beispiel neu mit Switch Expression:

return switch(personalId) {
     case 1, 2: yield "Fritz";
     case 3: yield "Hanswurst";
     default: yield "Defaultname";
 };

Dabei kann man seit Java 12 auch die Pfeile verwenden, was noch simpler ist:

return switch(personalId) {
     case 1, 2 -> "Fritz";
     case 3 -> "Hanswurst";
     default -> "Defaultname";
 };

Ehrlich gesagt muss ich zugeben, dass ich selber praktisch nie Switch-Case verwende. Aber mit den neuen Switch Expressions werde ich alles nur noch mit Switch machen, versprochen!

Was sind Text Blocks?

Text Blöcke vereinfachen den Umgang mit Strings, indem man einfach einen ganzen Block über mehrere Zeilen angeben kann, ohne nervige + und „\n“ (für einen Zeilenumbruch) Zeichen. So können zum Beispiel SQL Statements lesbarer dargestellt werden. Dies ist in anderen Programmiersprachen wie Groovy, Scala, Kotlin, C#, Swift oder Python bereits möglich.

Zum Erstellen eines Text Block verwendet man dreifache Anführungszeichen. Wichtig dabei: Die öffnenden dreifachen Anführungszeichen müssen in einer eigenen Zeile stehen!

So war es bisher:

String meineSeite = "<html>\n" + 
                    "<body>\n" + 
	            "<p>Meine super Seite!</p>\n" + 
		    "</body>\n" + 
		    "</html>";

Und so ist es mit Text Blöcken – Eine wunderbare Veränderung! Die Welt wird nie mehr sein wie früher! Menschen werden frohlockend auf den Strassen tanzen! Es werden öffentliche Fester gehalten, um dem Java Gott zu huldigen! Darum seid dankbar für dieses heilige Geschenk der Text Blöcke!

String meineSeite = """
                    <html> 
                      <body>
		        <p>Meine super Seite!</p>
		      </body>
		    </html>""";

Cool, gell? 🙂

Wie erhalte ich das umgekehrte Resultat für einen Java Streams Filter?

Java Streams sind eine feine Sache. Ich habe das Gefühl, ich fange mittlerweile alle meine Java Beiträge mit diesem Satz an. 🙂

Ein Java Streams Filter erlaubt es einem, auf einfache Weise alle gewünschten Einträge aus einer Liste herauszufiltern und den Rest wegzuschmeissen. Alle roten Elefanten, alle Kinder unter 6 Jahren, alle Schlangen mit Flügeln und so weiter.

Oftmals ist es aber einfacher zu sagen, was man nicht will. Als quasi ‚Schau her, ich selektiere alle Kinder zwischen 5 und 8 Jahren… und jetzt, du superkluges Java, gib mir alle anderen Kinder!

Wie macht man das?

Not Predicate definieren

Ganz einfach: Wo auch immer man den Filter umgekehren will definiert man ein Not Predicate. Predicate ist ja dieses Dings in dem Filter, und wenn das „true“ ist, wird der jeweilige Eintrag behalten. Und mit einem Not Predicate drehen wir die Logik einfach um.

Folgenden Code irgendwo hinschmeissen:

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

Und dann kann man das not im Filter verwenden.

Beispiel: Ihr habt eine Liste mit Kindernamen und findet den Namen „Rupert“ einfach wiederlich, den wollt Ihr unbedingt losweren. Also definieren wir zuerst einen Filter für den Namen ‚Rupert‘:

Arrays.stream(kindernamen).filter(kindername -> kindername.contains("Rupert")).collect(Collectors.toList());

Dieser Filter würde jetzt also alle Ruperts in der Resultateliste haben.

Dann kehren wir den Filter einfach um:

Arrays.stream(kindernamen).filter(not(kindername -> kindername.contains("Rupert"))).collect(Collectors.toList());

Et voilà.

Java 11 hats schon drin

Kleine Randbemerkung: In Java 11 ist genau diese Methode beim Predicate schon dabei:

Predicate.not( … )

Siehe https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/function/Predicate.html#not(java.util.function.Predicate)

Dann geht unser Rupert-Hass-Filter folgendermassen (und das Predicate muss man natürlich nicht mehr selber definieren):

Arrays.stream(kindernamen).filter(Predicate.not(kindername -> kindername.contains("Rupert"))).collect(Collectors.toList());

Wie verwende ich Java Streams bei zwei For Loops?

Streams sind wirklich eine feine Sache. So fein, dass ich heutzutage praktisch keine For-Loops mehr sehe. Streams sind auch tatsächlich einfach und am Ende sauschnell, um die Daten zu verarbeiten.

Aber wie verwende ich Streams, wenn ich zwei For Loops benötigen würde für die Verarbeitung? Also wenn ich eine „Liste in einer anderen Liste“ habe?

Die kurze Antwort ist:

  • Wenn man beim Verarbeiten der inneren Liste auf die Objekte der äusseren Liste zugreifen will, verwendet man zwei Streams() mit AnyMatch.
  • Wenn man beim Verarbeiten der inneren Liste nicht auf die Objekte der äusseren Liste zugreifen will, kann man FlatMap verwenden.

FlatMap? Was ist das schon wieder für eine Teufelei?

Ja schrecklich, nicht? Da denkst Du, Du hättest endlich mal alles wissenswerte über Java gelernt, und dann fange ich an, von FlatMaps zu schwafeln.

Um FlatMaps zu erklären fange ich gleich an mit dem Beispiel, dass ich auch unten im Code zeigen werden:

Ich habe eine Liste von Müttern und jede Mutter hat wiederum eine Liste von Ihren Kindern. Meine Aufgabe ist es, die Mutter mit dem Kind „Paul“ zu finden.

Die Daten könnten also folgendermassen aussehen:

Äussere Liste:

  • Mutter Janet
  • Mutter Petra

Janet hat zwei Kinder:

  • John
  • Jessica

Petra hat zwei Kinder:

  • Patrick
  • Paul

Wenn ich jetzt eine FlatMap verwende, erstelle ich für die Mütter einen Stream und erhalte dann einfach alle Kinder aneinandergereiht statt „pro Mutter“

Also statt

Janet { John, Jessica } Petra { Patrick, Paul }

erhalte ich

{ John, Jessica, Patrick, Paul }

Wenn ich jetzt die Information der Mutter nicht benötige und nur die Attribute eines Kindes prüfen kann (Beispielsweise: „Ist das Kind älter als 6 Jahre?“) dann funktioniert eine FlatMap sehr gut.

Sobald ich aber die Information der dazugehörigen Mutter brauche, geht das nicht mehr (Beispiel: „Zu welcher Mutter gehört das Kind Paul?“). Dann kann ich zwei Streams verwenden (siehe Codebeispiel unten).

Okay ich bin bereit. Bring mir den Code!

Der Code zeigt zuerst, wie man für Paul auf herkömmliche Weise herausfindet, wer seine Mutter ist. Dann verwende ich zwei Streams mit anyMatch im inneren Stream. Und am Ende noch eine FlatMap, wobei ich aber fälschlicherweise am Ende das Kind erhalte statt die Mutter.

Die Klassen für Mutter und Kind sowie das setzen der Werte finden sich ganz unten in der Klasse.

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class FindPaul {

List<Mother> mothers = new ArrayList<>();

public void findPaul() {
	setupMothers();
	twoForLoops();
	twoStreams();
	flatMap();	
}

------------------------------------------------------------

private void twoForLoops() {
	for(Mother  mother: mothers) {
		for (Child child: mother.children) {
			if("Paul".equals(child.name)) {
				System.out.println("1: " + mother.name + " is the mother of Paul");
			}
		}
	}
}

------------------------------------------------------------

private void twoStreams() {
	Optional<Mother> paulsMother = mothers.stream().filter(
		mother -> mother.children.stream().anyMatch(child -> "Paul".equals(child.name))				
	).findFirst();				
	System.out.println("2: " + paulsMother.get().name + " is the mother of Paul");		
}

------------------------------------------------------------

// flatMap makes the conversion: 
// Stream<List<Object>>	-> flatMap ->	Stream<Object>
private void flatMap() {
	// Fehler: Mit Flatmap hat man keinen Bezug mehr auf das "Parent" Element
	// Man kriegt ein "Child", nicht die Mutter
	Optional<Child> paulsMother = mothers.stream().flatMap(mother -> mother.children.stream()).filter(child -> "Paul".equals(child.name)).findFirst();					
	System.out.println("3: " + paulsMother.get().name + " is Paul itself, and who is the mother?");
}

------------------------------------------------------------

private void setupMothers() {
	Mother m1 = new Mother("Janet");
	m1.children.add(new Child("John"));
	m1.children.add(new Child("Jessica"));
	
	Mother m2 = new Mother("Petra");
	m2.children.add(new Child("Patrick"));
	m2.children.add(new Child("Paul"));
	
	mothers.add(m1);
	mothers.add(m2);
}

class Child {
	String name;		
	Child(String name) { this.name = name; }
}

class Mother {
	String name;
	List<Child> children = new ArrayList<>();
	Mother(String name) { this.name = name; }
}	
}

Übrigens: Falls jemand eine noch bessere Methode hat, um zwei For Loops zu verarbeiten, bin ich jederzeit bereit, etwas dazuzulernen. 🙂

AnyMatch und AllMatch

Noch ein kurzer Nachtrag: Wenn man zwei For Loops durch Streams ersetzt und dabei die Lösung verwendet, bei der man in der inneren Schleife die äusseren Objekte kennen muss (oben im Code die Methode twoStreams()) dann muss man noch aufpassen, dass man tatsächlich das richtige herausfiltert.

Bei dem angegebenen Beispiel mit „AnyMatch“ muss im inneren Stream einfach ein Kind „Paul“ heissen.

Will man aber, dass alle Kinder „Paul“ heissen, muss man statt AnyMatch „AllMatch“ verwenden. Der Rest des Codes bleibt der gleiche.