Die Java Local Variable Type Inference (LVTI) oder kurz der Var- Typ (der Var- Bezeichner ist kein Schlüsselwort, sondern ein reservierter Typname) wurde Java 10 mithilfe von JEP 286: Local-Variable Type Inference hinzugefügt. Da es sich um eine 100% ige Compilerfunktion handelt, hat dies keinen Einfluss auf Bytecode, Laufzeit oder Leistung. Grundsätzlich überprüft der Compiler die rechte Seite des Zuweisungsoperators und ermittelt darauf basierend den spezifischen Typ der Variablen und ersetzt ihn dann durch var .
Darüber hinaus ist es nützlich, um die Ausführlichkeit von Boilerplate-Code zu reduzieren und den Programmierprozess selbst zu beschleunigen. Zum Beispiel ist es sehr praktisch, var evenAndOdd =...
anstelle von Map<Boolean, List<Integer>> evenAndOdd =...
zu schreiben Map<Boolean, List<Integer>> evenAndOdd =...
Das Aussehen von var bedeutet nicht, dass es immer und bequem ist, es überall zu verwenden. Manchmal ist es praktischer, mit Standardwerkzeugen zu arbeiten.
In diesem Artikel werden 26 Situationen betrachtet, mit Beispielen, wann Sie var verwenden können und wann es sich nicht lohnt.
Punkt 1: Versuchen Sie, lokalen Variablen aussagekräftige Namen zu geben
Normalerweise konzentrieren wir uns darauf, den Feldern der Klassen die richtigen Namen zu geben, aber wir widmen den Namen lokaler Variablen nicht die gleiche Aufmerksamkeit. Wenn unsere Methoden perfekt implementiert sind, wenig Code enthalten und gute Namen haben, achten wir sehr oft nicht auf lokale Variablen oder reduzieren ihre Namen sogar vollständig.
Wenn wir var verwenden, anstatt explizite Typen zu schreiben, erkennt der Compiler diese automatisch und ersetzt var . Andererseits wird es dadurch für die Menschen schwieriger, den Code zu lesen und zu verstehen, da die Verwendung von var seine Lesbarkeit und sein Verständnis erschweren kann. In den meisten Fällen liegt dies daran, dass wir den Typ einer Variablen als primäre Information und ihren Namen als sekundäre betrachten. Obwohl es genau das Gegenteil sein sollte.
Beispiel 1:
Viele werden wahrscheinlich zustimmen, dass im folgenden Beispiel die Namen lokaler Variablen zu kurz sind:
Bei Verwendung von Kurznamen zusammen mit var wird der Code noch weniger klar:
Bevorzugtere Option:
Beispiel 2:
Vermeiden Sie es, Variablen wie folgt zu benennen:
Verwenden Sie aussagekräftigere Namen:
Beispiel 3:
Gehen Sie nicht zu Extremen, um lokalen Variablen verständlichere Namen zu geben:
Stattdessen können Sie eine kürzere, aber nicht weniger verständliche Option verwenden:
Wussten Sie, dass Java eine innere Klasse namens hat?
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState
Das Benennen von Variablen mit diesem Typ kann schwierig sein :)
Punkt 2: Verwenden Sie Literale, um var dabei zu helfen, den Typ des Grundelements zu bestimmen (int, long, float, double).
Ohne die Verwendung von Literalen für primitive Typen können wir feststellen, dass erwartete und implizite Typen unterschiedlich sein können. Dies wird durch die implizite Typkonvertierung verursacht, die von var- Variablen verwendet wird.
Beispielsweise verhalten sich die folgenden zwei Codefragmente wie erwartet. Hier deklarieren wir explizit die Typen Boolean und Char :
boolean flag = true;
Jetzt verwenden wir var , anstatt explizit Typen zu deklarieren:
var flag = true;
So weit so gut. Machen Sie jetzt dasselbe für die Typen int , long , float und double :
int intNumber = 20;
Obwohl das obige Code-Snippet einfach und unkompliziert ist, verwenden wir jetzt var, anstatt explizit Typen anzugeben.
Vermeiden Sie:
Alle vier Variablen werden als int ausgegeben. Um dieses Verhalten zu beheben, müssen wir Java-Literale verwenden:
Aber was passiert, wenn wir eine Dezimalzahl deklarieren?
Vermeiden Sie dies, wenn Sie eine Variable vom Typ float erwarten:
Verwenden Sie das entsprechende Literal, um Überraschungen zu vermeiden:
Punkt 3: In einigen Fällen können var- und implizite Typkonvertierungen die Codeunterstützung vereinfachen
Nehmen wir zum Beispiel an, dass unser Code zwischen zwei Methoden liegt. Eine Methode erhält einen Einkaufswagen mit verschiedenen Produkten und berechnet den besten Preis. Dazu vergleicht er verschiedene Marktpreise und gibt den Gesamtpreis in Form eines Float- Typs zurück. Eine andere Methode zieht diesen Preis einfach von der Karte ab.
Schauen wir uns zunächst eine Methode an, mit der der beste Preis berechnet wird:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
Zweitens werfen wir einen Blick auf die Methode, die mit der Karte funktioniert:
public boolean debitCard(float amount, ...) { ... }
Jetzt setzen wir unseren Code als Client zwischen diese beiden externen Servicemethoden. Unsere Benutzer können die zu kaufenden Waren auswählen und wir berechnen den besten Preis für sie und schreiben dann das Geld von der Karte ab:
Nach einiger Zeit beschließt das Unternehmen, dem die API gehört, die wesentliche Darstellung der Preise zugunsten der Dezimalstelle aufzugeben (anstelle von float wird jetzt int verwendet). Daher haben sie den API-Code wie folgt geändert:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
Tatsache ist, dass unser Code eine explizite Deklaration einer Float- Variablen als Preis verwendet. In der aktuellen Form erhalten wir beim Kompilieren einen Fehler. Wenn wir jedoch eine solche Situation vorausgesehen und var anstelle von float verwendet hätten , würde unser Code dank der impliziten Typkonvertierung ohne Probleme weiter funktionieren:
Punkt 4: Wenn Literale keine geeignete Lösung sind, verwenden Sie explizites Casting oder verwerfen Sie var
Einige primitive Typen in Java haben keine speziellen Literale, z. B. Byte- und Kurztypen. In diesem Fall können wir mithilfe der expliziten Typbezeichnung problemlos Variablen erstellen.
Verwenden Sie dies anstelle von var :
Aber warum bevorzugen Sie in dieser Situation die explizite Typennotation, anstatt nur var zu verwenden ? Nun, schreiben wir diesen Code mit var . Beachten Sie, dass der Compiler in beiden Fällen davon ausgeht, dass Sie Variablen vom Typ int benötigen.
Vermeiden Sie diesen Fehler:
Es gibt hier keine Literale, die uns helfen könnten, daher sind wir gezwungen, eine explizite Abwärtskonvertierung zu verwenden. Persönlich werde ich solche Situationen vermeiden, da ich hier keine Vorteile sehe.
Verwenden Sie diesen Eintrag nur, wenn Sie var wirklich verwenden möchten:
Der Vorteil der Verwendung von var besteht darin, präziseren Code zu schreiben. Im Fall der Verwendung von Konstruktoren können wir beispielsweise vermeiden, dass der Klassenname wiederholt werden muss, und daher die Code-Redundanz beseitigen.
Vermeiden Sie Folgendes:
Verwenden Sie stattdessen:
Für die folgende Konstruktion ist var auch eine gute Möglichkeit, den Code zu vereinfachen, ohne Informationen zu verlieren.
Vermeiden Sie:
Verwenden Sie den folgenden Code:
Warum arbeiten wir in den vorgestellten Beispielen besser mit var ? Weil alle notwendigen Informationen in den Namen der Variablen enthalten sind. Wenn var jedoch in Kombination mit einem Variablennamen die Klarheit des Codes verringert, ist es besser, die Verwendung abzulehnen.
Vermeiden Sie:
Verwendung:
Betrachten Sie beispielsweise die Verwendung der Klasse java.nio.channels.Selector
. Diese Klasse verfügt über eine statische open()
-Methode, die einen neuen Selector zurückgibt und ihn öffnet. Aber hier können Sie leicht denken, dass die Selector.open()
-Methode einen booleschen Typ zurückgeben kann, abhängig vom Erfolg beim Öffnen eines vorhandenen Selektors, oder sogar void zurückgeben kann . Die Verwendung von var führt hier zu Informationsverlust und Verwirrung im Code.
Punkt 6: var type garantiert die Sicherheit bei der Kompilierung
Dies bedeutet, dass wir keine Anwendung kompilieren können, die versucht, falsche Zuweisungen auszuführen. Der folgende Code wird beispielsweise nicht kompiliert:
Aber dieser kompiliert:
var items = 10; items = 20;
Und dieser Code wird erfolgreich kompiliert:
var items = "10"; items = "10 items";
Sobald der Compiler den Wert der Variablen var definiert hat, können wir nichts anderes als diesen Typ zuweisen.
Punkt 7: var kann nicht verwendet werden, um einen bestimmten Typ zu instanziieren und ihn einer Schnittstellentypvariablen zuzuweisen
In Java verwenden wir den Ansatz "Programmieren mit Schnittstellen". Zum Beispiel erstellen wir eine Instanz der ArrayList-Klasse und ordnen sie einer Abstraktion (Schnittstelle) zu:
List<String> products = new ArrayList<>();
Und wir vermeiden Dinge wie das Binden eines Objekts an eine Variable des gleichen Typs:
ArrayList<String> products = new ArrayList<>();
Dies ist die häufigste und wünschenswerteste Vorgehensweise, da wir die Schnittstellenimplementierung problemlos durch eine andere ersetzen können. Dazu muss nur eine Schnittstellentypvariable deklariert werden.
Wir werden diesem Konzept nicht folgen können, wenn wir var-Variablen verwenden, as Für sie wird immer ein bestimmter Typ angezeigt. Im folgenden Codeausschnitt bestimmt der Compiler beispielsweise den Typ der Variablen als ArrayList<String>
:
var productList = new ArrayList<String>();
Es gibt mehrere Verteidigungsargumente, die dieses Verhalten erklären:
var wird für lokale Variablen verwendet, wobei in den meisten Fällen die Programmierung über Schnittstellen weniger verwendet wird als in Fällen mit Methodenparametern, die von Werten oder Feldern zurückgegeben werden
Der Umfang lokaler Variablen sollte klein sein, daher sollte die Lösung von Problemen, die durch den Wechsel zu einer anderen Implementierung verursacht werden, nicht sehr schwierig sein
var behandelt den Code auf der rechten Seite als Initialisierer, mit dem der tatsächliche Typ bestimmt wird. Wenn irgendwann der Initialisierer geändert wird, kann sich auch der zu definierende Typ ändern, was zu Problemen im Code führt, der auf dieser Variablen beruht.
Absatz 8: Die Wahrscheinlichkeit des Abschlusses eines unerwarteten Typs
Die Verwendung von var in Kombination mit einem Diamantoperator (<>) ohne Informationen zur Identifizierung des Typs kann zu unerwarteten Ergebnissen führen.
Vor Java 7 wurde für Sammlungen eine explizite Typinferenz verwendet:
Beginnend mit Java 7 wurde der Diamantoperator eingeführt. In diesem Fall leitet der Compiler unabhängig den erforderlichen Typ ab:
Welcher Typ wird im folgenden Code ausgegeben?
Sie sollten solche Konstruktionen vermeiden:
Der Typ wird als ArrayList<Object>
. Dies liegt daran, dass die zur korrekten Bestimmung des Typs erforderlichen Informationen nicht bereitgestellt werden. Dies führt dazu, dass der nächstgelegene Typ ausgewählt wird, der mit dem Kontext des Geschehens kompatibel sein kann. In diesem Fall Object
.
Daher kann var nur verwendet werden, wenn wir die erforderlichen Informationen zur Bestimmung des erwarteten Typs bereitstellen. Der Typ kann direkt angegeben oder als Argument übergeben werden.
Geben Sie direkt den Typ an:
Übergeben Sie Argumente des erforderlichen Typs:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
Punkt 9: Das Zuweisen eines Arrays zu einer var-Variablen erfordert keine Klammern []
Wir alle wissen, wie man Arrays in Java deklariert:
int[] numbers = new int[5];
Wie wäre es mit var bei der Arbeit mit Arrays? In diesem Fall müssen keine Klammern auf der linken Seite verwendet werden.
Vermeiden Sie Folgendes (dies wird nicht einmal kompiliert):
Verwendung:
Der folgende Code mit var kann ebenfalls nicht kompiliert werden. Dies liegt daran, dass der Compiler den Typ nicht von der rechten Seite bestimmen kann:
Punkt 10: var kann nicht verwendet werden, wenn mehrere Variablen in derselben Zeile deklariert werden
Wenn Sie Variablen desselben Typs gleichzeitig deklarieren möchten, müssen Sie wissen, dass var dafür nicht geeignet ist. Der folgende Code wird nicht kompiliert:
Verwenden Sie stattdessen:
Oder ist es:
Punkt 11: Lokale Variablen sollten sich bemühen, ihren Umfang zu minimieren. Der var-Typ verstärkt diese Aussage.
Halten Sie einen kleinen Bereich für lokale Variablen ein - ich bin sicher, Sie haben diese Anweisung vor var gehört .
Lesbarkeit und schnelle Fehlerbehebungen sprechen für diesen Ansatz. Definieren wir beispielsweise einen Stapel wie folgt:
Vermeiden Sie dies:
Beachten Sie, dass wir die forEach()
-Methode aufrufen, die von java.util.Vector
geerbt wird. Diese Methode durchläuft den Stapel wie jeder andere Vektor, und genau das brauchen wir. Aber jetzt haben wir uns entschieden, ArrayDeque
anstelle von Stack
. Wenn wir dies tun, erhält die forEach()
-Methode eine Implementierung von ArrayDeque, die den Stapel als Standardstapel (LIFO) durchläuft.
Das wollen wir nicht. Es ist zu schwierig, den Fehler hier zu verfolgen, da sich der Code, der den forEach()
enthält, nicht neben dem Code befindet, in dem die Änderungen vorgenommen wurden. Um die Geschwindigkeit beim Suchen und Beheben von Fehlern zu erhöhen, ist es viel besser, Code mit der stack
so nah wie möglich an der Deklaration dieser Variablen zu schreiben.
Dies geschieht am besten wie folgt:
Wenn der Entwickler jetzt von Stack
zu ArrayQueue
, kann er den Fehler schnell erkennen und beheben.
Klausel 12: Der var-Typ vereinfacht die Verwendung verschiedener Typen in ternären Operatoren
Wir können verschiedene Arten von Operanden auf der rechten Seite des ternären Operators verwenden.
Bei der expliziten Angabe von Typen wird der folgende Code nicht kompiliert:
Trotzdem können wir das tun:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Der folgende Code wird auch nicht kompiliert:
Sie können jedoch allgemeinere Typen verwenden:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
In all diesen Fällen ist es besser, var zu bevorzugen:
Aus diesen Beispielen folgt nicht, dass der var- Typ zur Laufzeit Objekttypen definiert. Es ist nicht so!
Und natürlich funktioniert der var- Typ mit denselben Typen beider Operanden korrekt:
Punkt 13: Der Var-Typ kann in Schleifen verwendet werden
Wir können die explizite Deklaration von Typen in for- Schleifen leicht durch den Typ var ersetzen.
Ändern eines expliziten int- Typs in var :
Ändern des expliziten Order
in var :
List<Order> orderList = ...;
Punkt 14: var funktioniert gut mit Streams in Java 8
Es ist sehr einfach, var aus Java 10 mit Streams zu verwenden, die in Java 8 angezeigt wurden.
Sie können die explizite Deklaration vom Typ Stream einfach durch var ersetzen:
Beispiel 1:
Beispiel 2:
Klausel 15: var kann verwendet werden, wenn lokale Variablen deklariert werden, die große Ausdrucksketten in Teile zerlegen sollen
Ausdrücke mit viel Verschachtelung sehen beeindruckend aus und wirken normalerweise wie intelligente und wichtige Codeteile. In dem Fall, dass die Lesbarkeit des Codes verbessert werden muss, wird empfohlen, einen großen Ausdruck mithilfe lokaler Variablen aufzubrechen. Aber manchmal scheint das Schreiben vieler lokaler Variablen ein sehr anstrengender Job zu sein, den ich vermeiden möchte.
Ein Beispiel für einen großen Ausdruck:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Teilen Sie den Code besser in seine Bestandteile auf:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Die zweite Version des Codes sieht lesbarer und einfacher aus, aber die erste Version hat auch ein Existenzrecht. Es ist absolut normal, dass sich unser Geist an das Verständnis derart großer Ausdrücke anpasst und sie lokalen Variablen vorzieht. Die Verwendung des var- Typs kann jedoch dazu beitragen, große Strukturen aufzubrechen, indem der Aufwand für die Deklaration lokaler Variablen verringert wird:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Klausel 16: var kann nicht als Rückgabetyp oder als Methodenargumenttyp verwendet werden
Die beiden unten gezeigten Codefragmente werden nicht kompiliert.
Verwenden von var als Rückgabetyp:
Verwenden von var als Art von Methodenargument:
Klausel 17: Lokale Variablen vom Typ var können als Parameter der Methode übergeben werden oder den von der Methode zurückgegebenen Wert annehmen
Die folgenden Codefragmente werden kompiliert und funktionieren ordnungsgemäß:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
… Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . – :
Fazit
« var », Java 10. , . , var , .
var Java!