Test the best

Best of Java 10: Syntax-Erweiterungen – <em>var</em> & Co.

Best of Java 10: Syntax-Erweiterungen – <em>var</em> & Co.

Test the best

Best of Java 10: Syntax-Erweiterungen – <em>var</em> & Co.


Nur rund sechs Monate nach dem Erscheinen des umfangreichen und bedeutsamen Updates Java 9 erblickte Java 10 im März 2018 das Licht der Welt. Da Java 11 gerade erschienen ist, ergreift Michael Inden die Gelegenheit und wirft in dieser Artikelserie einen Blick auf die wichtigsten Features des Vorgängers.

Michael Inden hat bereits die Highlights von Java 9 in seiner Artikelserie Java 9 – Eine Einführung vorgestellt. Die vorliegende Artikelserie setzt sich aus Texten zusammen, die auch in seinem neuen Buch zu Java 10/11, das beim dpunkt.verlag erscheinen wird.

Aufgrund der Kürze der Zeit zwischen den Releases 9 und 10 enthält Java 10 lediglich wenige Änderungen und Erweiterungen. Die am meisten beachtete Neuerung ist vermutlich die sogenannte Local Variable Type Inference, besser bekannt als das reservierte Wort var (es ist tatsächlich kein Schlüsselwort, sondern wird nur speziell ausgewertet). Zudem finden sich einige API-Neuerungen bei Collections, Kollektoren und der Klasse java.util.Optional<T>.

Erwähnenswert ist auch, dass es sich bei Java 10 ebenso wie bei Java 9 lediglich um ein kurzzeitig verfügbares Release handelt, das mit Erscheinen von Java 11 in diesem Monat nicht länger unterstützt und Bugfixes erhalten wird.

Syntax-Erweiterungen

In Java 10 gibt es eine Syntax-Erweiterung: Die Local Variable Type Inference erlaubt es, auf die explizite Typangabe auf der linken Seite einer Variablendefinition zu verzichten und stattdessen dort var als Typplatzhalter zu schreiben. Das ist möglich, sofern der konkrete Typ für eine lokale Variable anhand der Definition auf der rechten Seite der Zuweisung vom Compiler ermittelt werden kann. Schauen wir auf einige einführende Beispiele für diese Kurzschreibweise von Variablendefinitionen:


final var name = "Peter"; // var => String
final var chars = name.toCharArray() // var => char[]final var mike = new Person("Mike", 47); // var => Person
final var hash = mike.hashCode(). // var => int

Im Zusammenhang mit generischen Containern spielt die Local Variable Type Inference ihre Vorteile aus:


// var => ArrayList<String> 
	final var names = new ArrayList<String>();
	names.add("Tim");
	names.add("Tom");
	names.add("Mike");

// var => Map<String, Long> final var personAgeMapping = Map.of("Tim", 47L, "Tom", 12L, "Michael", 47L, "Max", 25L);

Insbesondere wenn die Typangaben mehrere generische Parameter umfassen, kann var den Sourcecode deutlich kürzer und mitunter lesbarer machen. Betrachten wir als Beispiel eine Verschachtelung von Typen analog zu den Folgenden:

  • Set<Map.Entry<String, Long>>
  • Map<Character, Set<Map.Entry<String, Long>>>

In solchen Fällen hilft var einiges an Schreibarbeit einzusparen:


// var => Set<Map.Entry<String, Long>>
final var entries = personAgeMapping.entrySet();

	// var => Map<Character, Set<Map.Entry<String, Long>>> 
	final var filteredPersons = personAgeMapping.entrySet().stream().
		collect(groupingBy(firstChar,filtering(isAdult, toSet())));

Falls man sich bezüglich des konkreten Typs einmal unsicher ist oder man diesen doch einmal benötigt, ist der intelligente Tool-Tipp in Eclipse hilfreich, wie es folgende Abbildung zeigt:

Für die vermutlich eher seltenen Fälle, in denen man das Bedürfnis hat, den konkreten Typ statt var zu nutzen, existieren Quick-Fixes in den gebräuchlichen IDEs. Praktischerweise kann man damit zwischen konkreten Typ und var auch leicht hin und her wechseln.

Eine Kleinigkeit sollten wir noch betrachten. Im obigen Beispiel kommen folgende zwei Lambda-Ausdrücke zum Einsatz, um die Funktionalität zu realisieren:


final Function<Map.Entry<String, Long>, Character> firstChar = 
	entry -> entry.getKey().charAt(0);


final Predicate<Map.Entry<String, Long>> isAdult = 

	entry -> entry.getValue() >= 18;

Wäre es nicht wünschenswert, auch hier die Typangabe mit var abzukürzen? Eigentlich ja! Warum dies nicht geht, erkläre ich in den folgenden Abschnitten.

Beschränkungen

Rekapitulieren wir kurz: var ist für lokale Variablen gedacht, die direkt initialisiert werden und somit im Speziellen für Variablen in for und try-with-resources. Es scheint mitunter wünschenswert, var zur Deklaration von Attributen, Parametern oder Rückgabetypen nutzen zu können. Das geht jedoch nicht, weil hier der Typ vom Compiler nicht eindeutig ermittelt werden kann.

Beim Einsatz von var sollte man wissen, dass immer der exakte Typ verwendet wird und nicht ein Basistyp, wie man es für Collections getreu dem Paradigma „Program againt interfaces“ sehr gerne macht. Beachten Sie bitte, dass im Folgenden die Variable names deshalb nicht vom Typ List<String> ist, sondern vom Typ ArrayList<String>:


// var => ArrayList<String>
var names = new ArrayList<String>(); 
names = new LinkedList<String>(); // Compile Error

Die Zuweisung einer LinkedList<String> an die Variable names vom durch var repräsentierten Typ ArrayList<String> erzeugt folgende Fehlermeldung:

„Type mismatch: cannot convert from LinkedList<String> to ArrayList<String>

Es gibt weitere Dinge, die zu Kompilierfehlern führen:


var justDeclaration;     // keine Wertangabe / Definition
var numbers = {0, 1, 2}; // fehlende Typangabe
var appendSpace = str -> str + " "; // Typ unklar

Lambda-Ausdrücke und „var“

Kommen wir nochmals auf die zuvor kurz gezeigten beiden Lambda-Ausdrücke zurück, um Probleme bei deren Umwandlung in var aufzudecken:


final Function<Map.Entry<String, Long>, Character> firstChar = 
	entry -> entry.getKey().charAt(0);


final Predicate<Map.Entry<String, Long>> isAdult = 

	entry -> entry.getValue() >= 18;

Der Compiler kann den konkreten Typ rein basierend auf diesen Lambdas nicht ermitteln und somit ist keine Umwandlung in var möglich, sondern führt zur Fehlermeldung

„Lambda expression needs an explicit target-type“.

Wollte man diesen Fehler vermeiden, so müsste man folgenden Cast einfügen:


var isAdultVar = (Predicate<Map.Entry<String, Long>>)
	entry -> entry.getValue() >= 18;

Insgesamt sieht man, dass var für Lambda-Ausdrücke eher ungeeignet ist.

Fallstrick

Auf einen Fallstrick beim Einsatz von var möchte ich unbedingt noch eingehen: Manchmal ist man versucht, ohne viel Nachdenken die Typangabe auf der linken Seite direkt durch var zu ersetzen. Schauen wir auf ein harmlos wirkendes Beispiel einer auf String typisierten Liste:


final List<String> names = new ArrayList<>();
names.add("Expected");
// names.add(42); // Compile error

Hier nutzen wir auf der rechten Seite den sogenannten Diamond Operator, der es erlaubt, auf die explizite Angabe des generischen Typs zu verzichten. Das ist möglich, weil dieser aus der linken Seite der Variablendeklaration, genauer der Typangabe, hergeleitet werden kann. Nehmen wir an, wir würden nun die Angabe von List<String> durch var ersetzen und den zweiten Aufruf von add() einkommentieren:


final var mixedContent = new ArrayList<>();
mixedContent.add("Strange with var");
mixedContent.add(42);

Kompiliert und funktioniert das? Und wenn ja, was ist daran problematisch? Tatsächlich produziert das Ganze keinen Kompilierfehler. Wie kommt das? Aufgrund des Diamond Operators, bzw. der nicht vorhandenen Typangabe, stehen dem Compiler nicht ausreichend Typinformationen zur Verfügung: Deswegen wird Object als generischer Parameter genutzt und aus der zuvor auf String typisierten Liste wird – wohl eher unerwartet – eine ArrayList<Object>!

Die gezeigte Modifikation stellt einen Flüchtigkeitsfehler dar, der schwerwiegende Folgen haben kann. Genau deshalb steht der Umwandlungs-Quick-Fix in den IDEs nur dann zur Verfügung, wenn auf der linken Seite der exakte Typ angegeben wurde. In der Praxis ist das aber, durch die oftmals sinnvolle Designregel gegen Interfaces zu programmieren, vor allem für Collections eher selten der Fall: Regelkonform arbeitet man bei der Variablendeklaration vielfach mit einem Interface statt der konkreten Klasse. Bevor Sie etwas übereifrig dann selbst Hand anlegen und var nutzen, fügen Sie bitte die Typangabe auf der rechten Seite ein.

Michael Inden

Michael Inden ist Java- und Python-Enthusiast mit über zwanzig Jahren Berufserfahrung. Er hat bei diversen internationalen Firmen in verschiedenen Rollen etwa als Software-entwickler, -architekt, Teamleiter, CTO und Trainer gearbeitet. Derzeit ist er als Head of Development tätig. Darüber hinaus spricht er auf Konferenzen und schreibt Fachbücher wie "Java 21 LTS bis 23 -- Coole neue Java-Features: Modernes Java leicht gemacht", "Java 21 LTS -- Coole neue Java-Features: Update auf Java 22 inkludiert", "Der Weg zum Java-Profi", "Java – Die Neuerungen in Version 17 LTS, 18 und 19" sowie die Pärchen "Java Challenge» / «Python Challenge" und "Einfach Java" / "Einfach Python".