Java var-Schlüsselwort (Typinferenz für lokale Variablen)
var für lokale Variablen in Java: wann es die Lesbarkeit verbessert und wann nicht.
Seit Java 10 können Sie eine lokale Variable mit var deklarieren und den Compiler den Typ aus dem Initialisierer ableiten lassen. var greeting = "hello"; ist bytecode-technisch exakt dasselbe wie String greeting = "hello"; — der Typ ist weiterhin String, Sie haben ihn nur nicht zweimal geschrieben. Das ist Typinferenz für lokale Variablen: eine syntaktische Vereinfachung, die überflüssige Typnamen entfernt, ohne Java dynamisch typisiert zu machen. Richtig eingesetzt reduziert sie Lärm; leichtfertig angewendet verbirgt sie genau die Information, die ein Leser benötigt.
Diese Seite erklärt, was var tut und nicht tut, wo es genau erlaubt ist, wann es sich lohnt, wann es die Lesbarkeit beeinträchtigt, und enthält ein ausführbares Programm, das beweist, dass die abgeleiteten Typen das sind, was Sie erwarten.
var ist Inferenz, kein dynamisches Typisieren
Die wichtigste Tatsache zuerst: var ist kein neuer „Anything"-Typ. Der Compiler liest die rechte Seite, ermittelt den statischen Typ und baut ihn fest ein. Ab diesem Punkt ist die Variable genauso stark typisiert, als hätten Sie den Typ von Hand geschrieben — Sie können ihr keinen unverwandten Typ zuweisen, und der abgeleitete Typ ist zur Kompilierzeit festgelegt.
var name = "Ada"; // name has static type String, forever
name = "Lovelace"; // fine, still a String
name = 42; // compile error: int cannot be assigned to Stringvar ist ein reservierter Typname, kein Schlüsselwort — Sie können var weiterhin als Variablen- oder Methodenname verwenden (was Sie aber nicht sollten). Es löst die Inferenz nur in der Position einer lokalen Variablendeklaration aus.
Wo var erlaubt ist — und wo nicht
var funktioniert nur für lokale Variablen mit einem Initialisierer. Der Compiler benötigt eine rechte Seite, um den Typ zu lesen; ohne sie gibt es nichts abzuleiten.
| Position | var erlaubt? | Grund |
|---|---|---|
| Lokale Variable mit Initialisierer | Ja | Der Initialisierer liefert den Typ |
Index/Element in for-Schleifen | Ja | Der Schleifenausdruck liefert den Typ |
| Try-with-resources-Variable | Ja | Der Ressourcenausdruck liefert den Typ |
| Lokale Variable ohne Initialisierer | Nein | Nichts zum Ableiten vorhanden |
| Felder / Instanzvariablen | Nein | Inferenz ist designbedingt nur lokal |
| Methodenparameter | Nein | Aufrufer, nicht Initialisierer, liefern Werte |
| Methodenrückgabetypen | Nein | Gleicher Grund wie bei Parametern |
Nur mit null initialisiert | Nein | null hat keinen konkreten Typ |
| Lambda-Parameter (nackt) | Sonderfall | (var x, var y) -> ... ist seit Java 11 erlaubt |
var x; // error: cannot infer type, no initializer
var nothing = null; // error: null has no type to infer
public var field = 1; // error: var not allowed on fields
void m(var p) { } // error: var not allowed on parametersDer echte Vorteil: laute Generics zusammenfassen
var zahlt sich aus, wenn der Typname lang, wiederholt oder generics-lastig ist. Der klassische Fall ist eine Deklaration, bei der der Typ auf beiden Seiten des = vollständig erscheint:
// Before: the type name is written twice
Map<String, List<Customer>> byCity = new HashMap<String, List<Customer>>();
// After: the right side already says everything
var byCity = new HashMap<String, List<Customer>>();Es glänzt auch bei Iteratoren, dem Map.Entry, das Sie aus einer HashMap erhalten, und anderen ausführlichen Typen, die ausgeschrieben keine Klarheit hinzufügen:
for (var entry : byCity.entrySet()) { // Map.Entry<String, List<Customer>>
System.out.println(entry.getKey() + " -> " + entry.getValue().size());
}Wann man var NICHT verwenden sollte
var hilft, wenn der Typ aus der rechten Seite offensichtlich ist, und schadet, wenn das nicht der Fall ist. Wenn ein Leser den Code im Kopf ausführen muss, um den Typ zu kennen, schreiben Sie den Typ aus.
var result = service.process(input); // unclear: what does process return?
Order result = service.process(input); // clear: an Order
var flag = true; // fine, obviously boolean
var count = list.size(); // fine, obviously intBeachten Sie die Falle bei numerischen Literalen: var leitet den Typ des Literals ab, nicht den Typ, den Sie möglicherweise gemeint haben.
var n = 100; // int, not long — for a long you must write 100L or long n
var f = 3.14; // double, not float
byte b = 1; // explicit type narrows; var b = 1 would be intVermeiden Sie var, wenn dadurch ein bewusster Interface-Typ verloren geht. var list = new ArrayList<String>(); typisiert list als ArrayList<String>, nicht als List<String> — lokal in Ordnung, aber wenn Sie gegen das Interface programmieren möchten, sagen Sie das auch.
Ein ausgearbeitetes Beispiel zum Ausführen
Dieses Programm setzt var an allen erlaubten Positionen ein — einfache Werte, eine generische Map, eine erweiterte for-Schleife, eine indizierte for-Schleife — und verwendet getClass().getSimpleName(), um zu beweisen, dass die abgeleiteten Laufzeittypen genau das sind, was die rechten Seiten impliziert haben.
Was man aus dem Programmlauf mitnehmen sollte:
greeting.getClass().getSimpleName()gibtStringaus und beweist, dassvar greeting = "hello"einen echtenStringerzeugt hat —varist Kompilierzeit-Inferenz, und zur Laufzeit ist das Objekt genau das, was das Literal impliziert hat, nichts Dynamisches dabei.count + 1 = 43undprice * 2 = 19.98bestätigen die Inferenzregeln für Zahlen:42machtecountzu einemint,9.99machtepricezu einemdouble. Der Typ des Literals — nicht Ihre Absicht — entscheidet, was die Falle ist, an die man denken muss, wenn man einlongoderfloatbraucht.scores type = HashMapzeigt, dassvarden konkreten TypHashMapder rechten Seite erfasst hat, nicht dasMap-Interface; das Diamond<String, List<Integer>>auf der rechten Seite hat dem Compiler alles gegeben, was er brauchte, obwohl die linke Seite nurvarenthielt.total chars = 10stammt ausfor (var name : names), wonamealsStringabgeleitet wurde, sodassname.length()korrekt aufgelöst wurde (3 + 3 + 4) —varfunktioniert in erweiterten for-Schleifen und leitet den Elementtyp aus dem Iterable ab.0..4 sum = 10stammt ausfor (var i = 0; ...), woiaus dem Literal0alsintabgeleitet wurde; die indizierte Schleife ist einer der klarsten Orte fürvar, weil der Typ unverkennbar ist.
Übung
Verwandte Themen
- Java-Variablen — Deklarieren und Initialisieren lokaler Variablen.
- Gültigkeitsbereich von Variablen — warum
varbewusst auf den lokalen Gültigkeitsbereich beschränkt ist. - Generics — die ausführlichen Typnamen, bei deren Verbergen
varam besten ist. - Die
for-Schleife — wovar iundvar entrysauber lesbar sind.