Suche
Ist es denn schon wieder so weit?

Java 10 ist da! Die neuen Features auf einen Blick

Falk Sippach
java-10

© Shutterstock.com / morenina

Pünktlich zum vorab angekündigten Stichtag liefert Oracle das neue Java 10 aus. Wer große und bahnbrechende Veränderungen erwartet hat, wird zwar enttäuscht sein, aber es gibt durchaus eine ganze Menge toller neuer Features. Welche das sind, hat sich Falk Sippach, Trainer, Software-Entwickler und Projektleiter bei der OIO Orientation in Objects GmbH, im Detail angeschaut.

Java 10 ist final – Ist es denn schon wieder so weit?

Man stelle sich vor, die neueste Java-Version kommt pünktlich zum angekündigten Release-Termin heraus. Gibts nicht? Doch, gerade passiert (siehe hier und hier). Gefühlt wurde ja gerade erst Java 9 veröffentlicht, aber wie Anfang September 2017 angekündigt, ist nun gestern am geplanten Stichtag bereits die nächste Version erschienen. Bisher wurden die Java-Versionen Feature-getrieben entwickelt und da musste man schon mal ein paar Jahre auf das nächste Release warten. Bei den größeren Änderungen (Generics, Lambdas, Streams, Jigsaw) konnte man damit leben, die vielen kleinen nützlichen Sprachverbesserungen verzögerten sich so aber auch jedes Mal. Trotz kontroverser Diskussionen hat Oracle jetzt auf ein zeitbasiertes Modell umgestellt. Und so können wir Java-Entwickler uns nun alle 6 Monate auf eine neue Version freuen. Die Community ist über das neue Vorgehen allerdings zwiegespalten. Die großen Firmen schätzten bisher die Stabilität und die geringe Änderungsrate von Java.

Oracle reagierte auf die Bedenken und bietet weiterhin regelmäßig, aber in größeren Abständen Long Term Releases an. Nach Java 8 wird voraussichtlich Java 11 wieder einen Long Term Support erhalten. Java 9 (der Support ist gerade ausgelaufen) und Java 10 werden nur in dem halben Jahr bis zum nächsten Release unterstützt. Weitere Informationen findet man auf der Oracle Support Roadmap.

Darum ist es also auch nicht schlimm, dass wir Java 9 noch gar nicht richtig verdaut haben. Viele Tool- und Frameworkhersteller sind ebenfalls noch mit den Anpassungen auf das neue Modulsystem beschäftigt. Zum Beispiel hat JUnit gerade erst die Version 5.1 veröffentlicht, die Tests in Jigsaw-Modulen unterstützt. In der Enterprise-Java-Welt (Java EE, EE4J, Jakarta EE, MicroProfile) gibt es auch kaum Anzeichen dafür, wie die Modularisierung in Applikationsservern und Servlet-Containern Einzug halten soll.

Java 10 – Die neuen Features

Aber zurück zu Java 10, dank des kurzen Zeitrahmens muss man sich nicht durch allzu lange Release Notes quälen. Die Liste auf der OpenJDK-Projektseite ist überschaubar:

Featureliste Java 10

Aus Entwicklersicht ist hauptsächlich eine Neuerung interessant, der Java Enhancement Process (JEP) 286 zur Local-Variable Type Inference. Typinferenz ist das Schlussfolgern auf Datentypen aus den restlichen Angaben des Codes und den Typisierungsregeln heraus. Das spart Schreibarbeit und bläht den Quellcode nicht unnötig auf, was wiederum die Lesbarkeit erhöht. Wir kennen das in Java bereits bei Lambda-Parametern und beim Diamond-Operator für generische Datencontainer bzw. seit Java 9 auch für anonyme innere Klassen.

		// Type inference bei Parametern von Lambda Ausdrücken (Java 8)
		Function<String, String> helloFunction = s -> "Hello " + s;
		
		// Inference von Generics (Diamond Operator, seit Java 7)
		List<String> strings = new ArrayList<>();
		strings.add(helloFunction.apply("World"));
		
		// Inference von Generics (Diamond Operator) bei anonymen inneren Klassen (Java 9 -> JEP 213)
		Consumer<String> printer = new Consumer<>() {
			@Override
			public void accept(String string) {
				System.out.println(string);
			}
		};
		strings.forEach(printer::accept);

Mit dem Schlüsselwort var können jetzt sehr prägnant lokale Variablen definiert werden, deren Datentyp sich direkt aus der Zuweisung des Wertes ergibt. Während beim Diamond-Operator die Typinformation aus der linken Seite der Zuweisung geschlussfolgert wird, ist es bei Local-Variable Type Inference genau anders herum.

		// int
		var zahl = 5;
		// String
		var string = "Hello World";
		// BigDecimal
		var objekt = BigDecimal.ONE;

Einmal deklarierte var-Variablen sind auf den zugewiesenen Datentyp festgelegt. Möchte man nachträglich Werte anderer Typen zuweisen, erhält man Compiler-Fehler aufgrund der fehlschlagenden Typ-Konvertierung.

		// Type wird bei Deklaration und Erstzuweisung vom Compiler festgelegt
		var zahl = 5;
		zahl = 7L; // incompatible types: possible lossy conversion from long to int

		var objekt = BigDecimal.ONE;
		objekt = BigInteger.TEN; // incompatible types: BigInteger cannot be converted to BigDecimal

Bei der Deklaration muss also immer ein Wert zugewiesen werden, sonst kommt es ebenfalls zu Compiler-Fehlern, auch wenn wie im folgenden Beispiel kurz darauf garantiert ein Wert zugewiesen werden würde.


		// Deklaration von "var" nur bei direkter Initialisierung der Variable
		var flag = true;
		var number; // Compiler-Fehler
		if (flag) {
			number = 5;
		} else {
			number = 7;
		}

Mit var deklarierte Variablen sind wie der Name schon verdeutlicht, nicht automatisch unveränderbar. Man kann sie aber mit dem Schlüsselwort final kombinieren. Außerdem sind sie effectively final (wenn es nur eine Zuweisung gibt) und können somit auch aus inneren Klassen und Lambda-Ausdrücken verwendet werden, ohne sie explizit als final zu deklarieren.

		// Inference bei Neuzuweisung eines Wertes (var impliziert nicht "final")
		var zahl = 5;
		zahl = 7;

		// Inference auch mit "final" nutzbar, ansonsten gilt seit Java 8 ohnehin "effectively final Semantik"
		final var zahl = 5;

Die Typinferenz funktioniert auch mit generischen Typen und innerhalb der foreach-Schleife. Die Kombination mit dem Diamond-Operator führt aber aufgrund der fehlenden Typinformation zu einem Container von Object-Referenzen und somit zu weniger Typsicherheit.

// Inference generischer Typen (List<String>)
var strings = Arrays.asList("World", "Java 10");
    
// Inference in Schleifen
for (var string : strings) {
    System.out.println("Hello " + string);
}

// Kombination mit Diamond Operator führt zu Inferenz von List<Object>
var strings = new ArrayList<>();
strings.add("Hello World");
for (var string : strings) {
    System.out.println(string.replace("World", "Java 10")); // cannot find symbol 'replace'
}

var strings2 = new ArrayList<String>();
strings2.add("Hello World");
for (var string : strings2) {
    System.out.println(string.replace("World", "Java 10"));
}

Die Typinferenz nutzt im Übrigen den konkreten Typ, das heißt bei anonymen inneren Klassen dürfen zwei vom selben Interface abgeleitete Instanzen nicht der selben var-Variable zugewiesen werden.

// Inference nutzt konkrete Typisierung
var runnable = new Runnable() {
    @Override
    public void run() {
    }
};
// incompatible types: <anonymous Runnable> cannot be converted to <anonymous Runnable>
runnable = new Runnable() {
    @Override
    public void run() {
    }
};

Das bedeutet aber wiederum auch, dass bei lokalen Typen (anonymen inneren Klassenimplementierungen) neu implementierte Methoden ohne Compiler-Fehler aufgerufen werden können (reverseMe()).

			// Inference von lokalen Typen
			var myReversibleStringList = new ArrayList<String>() {
				List<String> reverseMe() {
					var reversed = new ArrayList<String>(this);
					Collections.reverse(reversed);
					return reversed;
				}
			};
			myReversibleStringList.add("World");
			myReversibleStringList.add("Hello");
			
			System.out.println(myReversibleStringList.reverseMe());

Für Java 11 wird noch an einer Erweiterung der Local-Variable Type Inference gearbeitet, die in Lambda-Ausdrücken funktioniert. Das ist für die Kombination von Typinferenz mit Typ-Annotationen notwendig.

			// Ausblick Java 11 (JEP 323)
			// Inference von Lambda Parametern
			Consumer<String> printer = (var s) -> System.out.println(s); // statt s -> System.out.println(s);

			// aber keine Mischung von "var" und deklarierten Typen möglich
			// BiConsumer<String, String> printer = (var s1, String s2) -> System.out.println(s1 + " " + s2);
			
			// Nützlich für Type Annotations
			BiConsumer<String, String> printer = (@Nonnull var s1, @Nullable var s2) -> System.out.println(s1 + (s2 == null ? "" : " " + s2));

Die restlichen Punkte der Release Notes betreffen eher den infrastrukturellen Bereich und den Betrieb von Java-Anwendungen. So kann der G1, der seit Java 9 der Standard Garbage Collector ist, die Full Garbage Collection parallelisieren und so die Stop-The-World-Zyklen verkürzen. Der Memory Footprint kann durch das Teilen von geladenen Klassen zwischen mehreren Java-Anwendungen verringert werden. Des Weiteren gibt es einen neuen, noch experimentellen JIT (Just-In-Time) Compiler (Graal) und Verbesserungen an der JVM für die Zusammenarbeit mit Docker Containern. Zudem wird der Trust Store des OpenJDK jetzt mit einer gewissen Menge an Root-Zertifikaten ausgeliefert, was sonst nur den Oracle Java SE Versionen vorbehalten war.

An der Klassen-Bibliothek (JDK) gibt es auch kleine Änderungen. Das umfasst beispielsweise eine überladene Version von orElseThrow() in der Klasse Optional und diverse Fabrikmethoden zur Erzeugung von unmodifiable Collections und Stream-Kollektoren. Weitere Änderungen kann man den Release Notes entnehmen oder über das Tool JDK-API-Diff herausfinden.

Ein unaufgeregtes Release

Schlussendlich lässt sich sagen, dass Java 10 ein unaufgeregtes Release ist. Beeindruckend ist aber, dass es wie angekündigt „Just-In-Time“ geliefert wurde. Nun muss sich in den nächsten Jahren zeigen, wie die Java-Welt dieses neue Auslieferungsmodell annimmt. Als Entwickler dürfen wir jetzt häufiger mit kleinen nützlichen Sprachfeatures spielen. Das sind auf jeden Fall gute Aussichten. Dann viel Spaß beim runterladen und ausprobieren. Und nicht vergessen: Im September steht mit Java 11 bereits das nächste Release auf dem Plan.

Verwandte Themen:

Geschrieben von
Falk Sippach
Falk Sippach
Falk Sippach hat über 20 Jahre Erfahrung mit Java und ist bei der Mannheimer Firma OIO Orientation in Objects GmbH als Trainer, Software-Entwickler und Projektleiter tätig.
Kommentare
  1. Java Developer2018-03-21 17:10:13

    Ich finde das Typinferenz und Lamda Ausdrücke die Codelesbarkeit eher verschlechtern.
    Es ist für mich ahnlich wie Variablen Namen abzukürzen. Es ist zwar kürzer, aber nicht lesbarer wenn man später drauf guckt.
    Ich finde Java entwickelt sich im Moment in eine falsche Richtung.
    Man sollte wichtigere Dinge (wie das Null Pointer Exception Problem) angehen, anstatt sowas.

  2. Java Developer2018-03-21 17:12:08

    *Ich finde dass Typinferenz und Lamda Ausdrücke die Codelesbarkeit eher verschlechtern.
    Es ist für mich ähnlich wie Variablen Namen abzukürzen. Es ist zwar kürzer, aber nicht lesbarer wenn man später drauf guckt.
    Ich finde Java entwickelt sich im Moment in eine falsche Richtung.
    Man sollte wichtigere Dinge (wie das Null Pointer Exception Problem) angehen, anstatt sowas.

  3. J.Sauerbier2018-03-22 18:05:33

    Da kann ich nur voll zustimmen. Die Lambda-Ausdrücke erschweren nicht nur die Lesbarkeit (dort, wo andere Programmimersprachen noch extra die Parameter bei Aufruf benennen, siehe Objective-C, wird hier noch die aufgerufene Methode ausgeblendet), sondern auch das Debugging.
    Es wird -- außer einer Zeile Code -- nicht wirklich etwas gespart.

    Auch die Verkürzung der Variablennamen (was ja nur für lokale Deklarationen gilt) bringt höchstens Ersparnis beim Tippe (was eine gute IDE aber auch erledigen kann, aber macht es noch fehleranfälliger beim Lesen.

    Es ist nichts gewonnen, sondern nur eine zusätzliche Fehlerquelle eingebaut.

  4. Oliver Skawronek2018-03-23 01:28:42

    Zum Gebrauch von var gibt es in C# bereits länger Diskussionen. In dem Blog-Artikel "Uses and misuses of implicit typing" https://blogs.msdn.microsoft.com/ericlippert/2011/04/20/uses-and-misuses-of-implicit-typing/ werden Tipps dazu gegeben, wann var benutzt und wann der Typ explizit angegeben werden soll.

    Wenn man in Java täglich programmiert, ist var sicherlich eine Erleichterung - sowohl beim Tippen (weniger Schreibarbeit) als auch beim Verstehen von Code (Konzentration auf die Geschäftslogik statt technischen Details). Java-Anfänger sollte man eher später mit var bekannt machen. Vergleichbar mit Lambdas, die für Anfänger kryptisch wirken aber in der Praxis viel Zeit einsparen.

    Noch eine Anmerkung zur Länge von Bezeichnern: Hier gilt die Regel, je größer der Scope ist, desto beschreibender muss der Name sein. Deswegen ist es in Ordnung, dass wir Zählvariablen "i" statt "customerIndex" nennen, weil sie nur innerhalb der Schleife (kleiner Scope) gültig sind. Eine Klasse (großer Scope) hingegen würden wir nie "I" nennen, sondern Aussagekräftige Namen, wie "Customer", vergeben.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.