Spickzettel zur lokalen Typinferenz für Java 10 und höher!
Stuart Marks
26. April 2018
0 Min. Lesezeit
Willkommen zur ersten Ausgabe einer neuen Reihe von Spickzetteln, die wir im Snyk-Blog veröffentlichen. Wir stellen Inhalte bereit, die Sie ausdrucken und aufhängen können, damit Sie sich als Entwickler weiter verbessern. In unserer ersten Ausgabe, kurz nach der Veröffentlichung von Java 10, beschäftigen wir uns mit der viel diskutierten Typinferenz für lokale Variablen. Sie können die PDF-Version herunterladen, indem Sie oben auf das Bild oder diesen Link klicken! Das Grundprinzip der lokalen Typinferenz ist ganz einfach: Ersetzen Sie den expliziten Typ in der Deklaration durch den neuen reservierten Typnamen „var“, und der Typ wird abgeleitet. Wir könnten also Folgendes ersetzen:
durch:
Der Typ von outputStream wird als ByteArrayOutputStream abgeleitet. Moment mal, erlauben wir damit jetzt dynamische Typisierung in Java? Auf keinen Fall! Die gesamte Typinferenz findet zur Kompilierzeit statt, und der Compiler schreibt die expliziten Typen in den Bytecode. Zur Laufzeit ist Java so statisch wie eh und je. Da die Verwendung so einfach ist, konzentriert sich dieser Spickzettel auf den wichtigsten Aspekt der lokalen Typinferenz – ihren praktischen Einsatz. Er gibt Hinweise dazu, wann Sie explizite Typen verwenden und wann Sie Typinferenz in Betracht ziehen sollten.
Seit ich diesen Spickzettel verfassen wollte, hat Stuart Marks, JDK-Entwickler bei Oracle, den perfekten Artikel veröffentlicht. Darin erläutert er sowohl Programmierprinzipien als auch den Einsatz von Typinferenz. Als ich also den Spickzettel erstellen wollte, wandte ich mich direkt an Stuart, um zu fragen, ob wir seine Gedanken einbeziehen und zu einem Spickzettel zusammenfassen könnten, den Entwickler aufhängen und täglich verwenden können. Ich empfehle Ihnen sehr, Stuarts Artikel vollständig zu lesen – Ihre Zeit ist es wirklich wert!
Prinzipien
1. Code lesen > Code schreiben.
Ob Sie nun 10 Minuten oder 10 Tage brauchen, um eine Codezeile zu schreiben: Sie werden sie mit ziemlicher Sicherheit noch viele Jahre lang lesen. Code ist nur dann langfristig wartbar und verständlich, wenn er klar und prägnant ist und vor allem alle Informationen enthält, die zum Verständnis seines Zwecks nötig sind. Ziel ist ein möglichst gutes Verständnis.
2. Code sollte sich durch lokale Analyse verstehen lassen.
Integrieren Sie so viele Informationen wie möglich in Ihren Code, damit Leser nicht verschiedene Teile der Codebasis durchsuchen müssen, um zu verstehen, was passiert. Das kann zum Beispiel durch die Benennung von Methoden oder Variablen geschehen.
3. Die Lesbarkeit des Codes sollte nicht von IDEs abhängen.
IDEs können großartig sein. Ich meine wirklich großartig! Sie können die Produktivität und Genauigkeit von Entwicklern steigern. Code muss jedoch auch ohne IDE lesbar und verständlich sein. Häufig wird Code außerhalb einer IDE gelesen. Oder IDEs bieten unterschiedlich viele Informationen. Code sollte selbsterklärend sein: Er sollte für sich genommen verständlich sein, ohne dass Werkzeuge helfen müssen.
Die Entscheidung liegt bei Ihnen.
Ob Sie einer Variablen einen expliziten Typ zuweisen oder den Java-Compiler den Typ ableiten lassen, ist ein Kompromiss. Einerseits möchten Sie überflüssigen Ballast, Boilerplate-Code und Formalitäten vermeiden. Andererseits soll die Verständlichkeit des Codes nicht leiden. Die Typdeklaration ist nicht die einzige Möglichkeit, Lesern Informationen zu vermitteln. Weitere Möglichkeiten sind der Variablenname und der Initialisierungsausdruck. Bei der Entscheidung, ob Sie für eine bestimmte Variable auf die explizite Typangabe verzichten können, sollten Sie alle verfügbaren Informationsquellen berücksichtigen.
Richtlinien
1. Wählen Sie Variablennamen, die nützliche Informationen vermitteln.
Das ist generell eine gute Praxis, im Zusammenhang mit var jedoch noch wichtiger. Bei einer var-Deklaration können Sie Informationen über die Bedeutung und Verwendung der Variablen durch ihren Namen vermitteln. Wenn Sie einen expliziten Typ durch var ersetzen, sollten Sie oft auch den Variablennamen verbessern. Manchmal kann es nützlich sein, den Typ der Variablen im Namen zu verankern. Zum Beispiel:
2. Begrenzen Sie den Gültigkeitsbereich lokaler Variablen.
Die Begrenzung des Gültigkeitsbereichs lokaler Variablen wird in Effective Java (3. Auflage), Punkt 57, beschrieben. Bei der Verwendung von var gilt das umso mehr. Das Problem entsteht, wenn der Gültigkeitsbereich einer Variablen groß ist, also viele Codezeilen zwischen ihrer Deklaration und ihrer Verwendung liegen. Bei späteren Änderungen am Code können Änderungen an Typen und Ähnlichem zu einem anderen Verhalten führen. Der Wechsel von einer List zu einem Set mag beispielsweise unproblematisch erscheinen. Aber ist Ihr Code später im selben Gültigkeitsbereich auf eine bestimmte Reihenfolge angewiesen? Typen werden zwar immer statisch festgelegt, doch feine Unterschiede zwischen Implementierungen derselben Schnittstelle können zu Problemen führen. Vermeiden Sie var in solchen Fällen nicht einfach. Ändern Sie stattdessen den Code so, dass der Gültigkeitsbereich der lokalen Variablen kleiner wird, und deklarieren Sie sie erst dann mit var. Sehen Sie sich den folgenden Code an:
Dieser Code enthält nun einen Fehler, da für Sets keine Iterationsreihenfolge festgelegt ist. Der Programmierer wird den Fehler jedoch wahrscheinlich sofort beheben, da die Verwendungen der Variablen items direkt auf ihre Deklaration folgen. Nehmen wir nun an, dieser Code ist Teil einer großen Methode, in der auch die Variable items einen entsprechend großen Gültigkeitsbereich hat:
Jetzt ist es viel schwieriger, den Fehler zu finden: Die Zeile, die versucht, ein Element am Ende des Sets hinzuzufügen, liegt zu weit von der Typdeklaration entfernt, als dass der Fehler sofort auffallen würde.
3. Ziehen Sie var in Betracht, wenn der Initialisierungsausdruck den Lesern genügend Informationen liefert.
Lokale Variablen werden häufig mit Konstruktoren initialisiert. Der Name der Klasse, deren Instanz erstellt wird, wiederholt sich oft als expliziter Typ auf der linken Seite. Ist der Typname lang, sorgt var für eine kürzere Schreibweise, ohne dass Informationen verloren gehen:
Es ist auch sinnvoll, var zu verwenden, wenn die Initialisierung durch einen Methodenaufruf erfolgt, etwa bei Files.newBufferedReader(…) oder List stringList = List.of("a", "b", "c").
4. Verwenden Sie var, um verkettete oder verschachtelte Ausdrücke mit lokalen Variablen aufzuteilen.
Betrachten Sie Code, der eine Sammlung von Zeichenfolgen entgegennimmt und die am häufigsten vorkommende Zeichenfolge ermittelt. Das könnte so aussehen:
Dieser Code ist korrekt, lässt sich aber über mehrere Anweisungen hinweg besser lesen. Das Problem beim Aufteilen in Anweisungen wie gezeigt:
Der Autor hat sich vermutlich dagegen entschieden, weil die expliziten Typangaben äußerst unübersichtlich wirken und vom wichtigen Code ablenken. Mit var können wir den Code natürlicher ausdrücken, ohne den hohen Preis expliziter Typdeklarationen für die Zwischenvariablen zu zahlen:
Man kann durchaus das erste Beispiel mit seiner einzelnen langen Kette von Methodenaufrufen bevorzugen. In manchen Fällen ist es jedoch besser, lange Methodenketten aufzuteilen.
5. Machen Sie sich bei lokalen Variablen nicht zu viele Gedanken über das „Programmieren gegen Schnittstellen“.
Eine gängige Redewendung in der Java-Programmierung ist, eine Instanz eines konkreten Typs zu erstellen, sie aber einer Variablen mit einem Schnittstellentyp zuzuweisen. Zum Beispiel:
Wird jedoch var verwendet, wird statt der Schnittstelle der konkrete Typ abgeleitet:
Code, der die Variable list verwendet, kann nun Abhängigkeiten von der konkreten Implementierung aufweisen. Ändert sich der Initialisierungsausdruck der Variablen später, kann sich dadurch auch ihr abgeleiteter Typ ändern. Das kann in nachfolgendem Code, der die Variable verwendet, Fehler oder Bugs verursachen.
Wenn Sie Richtlinie 2 befolgen, ist das weniger problematisch: Da der Gültigkeitsbereich der lokalen Variablen klein ist, sind die Risiken durch das „Durchsickern“ der konkreten Implementierung in nachfolgenden Code begrenzt.
6. Seien Sie vorsichtig, wenn Sie var mit dem Diamond-Operator oder generischen Methoden verwenden.
Sowohl var als auch das „Diamond“-Feature ermöglichen es, explizite Typinformationen wegzulassen, wenn sie sich aus bereits vorhandenen Informationen ableiten lassen. Werden beide gemeinsam verwendet, kann es jedoch passieren, dass alle nützlichen Informationen fehlen, die der Compiler benötigt, um den gewünschten Typ korrekt abzuleiten.
Sehen Sie sich Folgendes an:
Auch bei generischen Methoden ist die Typinferenz so erfolgreich eingesetzt worden, dass Programmierer nur selten explizite Typargumente angeben. Wenn tatsächliche Methodenargumente keine ausreichenden Typinformationen liefern, stützt sich die Typinferenz für generische Methoden auf den Zieltyp. Bei einer var-Deklaration gibt es keinen Zieltyp, sodass ein ähnliches Problem wie beim Diamond-Operator auftreten kann. Zum Beispiel:
Sowohl beim Diamond-Operator als auch bei generischen Methoden können die tatsächlichen Argumente des Konstruktors oder der Methode zusätzliche Typinformationen liefern, sodass der beabsichtigte Typ abgeleitet werden kann. Das fügt zwar eine zusätzliche Indirektionsebene hinzu, bleibt aber vorhersehbar. Daher gilt:
7. Seien Sie vorsichtig, wenn Sie var mit Literalen verwenden.
Die Verwendung von var mit Literalen bringt wahrscheinlich keinen großen Vorteil, da die Typnamen im Allgemeinen kurz sind. Manchmal ist var jedoch nützlich, zum Beispiel um Variablennamen auszurichten.
Bei booleschen Werten, Zeichen-, Long- und Zeichenfolgenliteralen gibt es kein Problem. Der aus diesen Literalen abgeleitete Typ ist eindeutig, sodass auch die Bedeutung von var klar ist. Besondere Vorsicht ist geboten, wenn es sich beim Initialisierungsausdruck um einen Zahlenwert handelt, insbesondere um ein Integer-Literal. Bei einem expliziten Typ auf der linken Seite kann der Zahlenwert unbemerkt in andere Typen als int erweitert oder verkleinert werden. Mit var wird der Wert als int abgeleitet, was möglicherweise nicht beabsichtigt ist.
Fazit
Die Verwendung von var in Deklarationen kann den Code verbessern, indem sie überflüssigen Ballast reduziert und wichtigere Informationen hervorhebt. Andererseits kann eine wahllose Verwendung von var den Code verschlechtern. Richtig eingesetzt, kann var guten Code verbessern und ihn kürzer und klarer machen, ohne die Verständlichkeit zu beeinträchtigen. Fragen Sie sich bei der Verwendung von var, ob Ihr Code dadurch mehrdeutig geworden ist oder ob er sich ohne allzu große Mühe klar verstehen lässt.
Laden Sie den Spickzettel jetzt herunter!
Starten Sie mit Capture the Flag
Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.


