Angenommen, wir haben eine Klasse wie diese:
%Vor%Es wird nicht kompiliert, obwohl es intuitiv scheint, als ob es "sollte":
%Vor% Der Grund dafür ist, dass foo
keinen gebundenen generischen Typ hat, daher weiß der Compiler nicht, dass die Ausgabe von foo.getValue()
mit der Eingabe von foo.setValue()
kompatibel ist.
Um das zu beheben, müssen Sie eine neue Methode erstellen, nur um den generischen Typparameter in der for()
Schleife zu binden:
Das hat mich immer geärgert. Außerdem scheint es eine einfache Lösung zu geben.
Meine Frage: Gibt es einen "guten" Grund, warum die Java-Sprache nicht erweitert werden sollte, um generische Typdeklarationen für Variablendeklarationen zuzulassen? Beispiel:
%Vor% oder, genauer gesagt, in diesem Fall einer for()
-Schleife:
Ich frage mich, ob ein Compiler-Assistent erklären kann, warum das entweder zu schwierig wäre oder (und sollte).
BEARBEITEN:
Als Antwort auf diese vorgeschlagene Lösung:
%Vor% Diese Methodensignatur lässt Foo
s mit verschiedenen generischen Typen nicht zusammen zurücksetzen. Mit anderen Worten, der Versuch, ein Iterable<Foo<?>>
zu übergeben, verursacht einen Kompilierungsfehler.
In diesem Beispiel wird das Problem veranschaulicht:
%Vor%Der Kompilierfehler lautet:
Methode
resetFoos
kann nicht auf bestimmte Typen angewendet werden;erforderlich:
Iterable<Foo<T>>
gefunden:
List<Foo<?>>
Grund: Keine Instanz (en) vom Typ Variable (n)
T
existieren, so dass der ArgumenttypList<Foo<?>>
dem formalen ParametertypIterable<Foo<T>>
entspricht. woT
eine Typvariable ist:T extends Object
deklariert in der Methode<T>resetFoos(Iterable<Foo<T>>)
(mit sun-jdk-1.7.0_10 über ideone.com)
Grundsätzlich können Typvariablen in Java nur zwei Bereiche haben: 1) Klassenbereich oder 2) Methodenbereich. Sie fragen, warum nicht einen anderen Bereich erlauben - Bereich eines lokalen Code-Block (in diesem Fall das Innere einer for-Schleife.
Ja, in manchen Fällen wäre das hilfreich. Und es wäre nicht schwer, es der Sprache hinzuzufügen. Dies sind jedoch ziemlich seltene Fälle, und es ist wahrscheinlicher, Menschen zu verwirren als zu helfen. Es gibt auch eine relativ einfache und effektive Problemumgehung, die Sie, wie Sie bereits festgestellt haben, in den lokalen Blockbereich auf eine private Hilfsfunktion verschieben, die dann eine Variable vom Typ des Methodenbereichs verwenden kann:
%Vor%Ja, dies könnte den Code durch zusätzliche Methodenaufrufe weniger effizient machen, aber das ist ein geringes Problem.
Sie können die Methode resetFoos
selbst generisch machen:
Auf diese Weise weiß der Compiler, dass die T
von foo.getValue()
die gleiche T
für foo.setValue()
ist.
Das ist nur die Problemumgehung. Ich weiß nicht, warum der Compiler nicht erlaubt, Generics auf einer Variablenebene wie final <T> Foo<T> typedFoo = foo;
; Ich weiß nur, dass Sie es nicht auf der Variablenebene deklarieren können. Hier ist jedoch die Methodenstufe ausreichend.
Es könnte wahrscheinlich gemacht werden, möglicherweise mit einigen Randfällen. Aber wirklich, der Grund, warum es nicht gemacht wurde, ist einfach, dass die JLS-Leute damit aufhörten.
Ein Typsystem kann nicht alles über Ihr Programm ableiten. Es wird eine Menge tun, aber es wird immer einen Platz geben, an dem ein Compiler / Typ-Checker nichts weiß, was der Mensch beweisen kann. Also müssen die Leute, die Compiler und Typ-Checker entwerfen, entscheiden, wie weit sie gehen sollen, wenn es darum geht, zu folgern und clever zu sein - und egal, wo sie aufhören, wird jemand immer eine Frage wie diese stellen können: "Warum didn '?" Gehen sie diesen zusätzlichen Schritt? "
Meine Vermutung - und es ist nur eine Vermutung - ist, dass diese Schlussfolgerung es nicht geschafft hat wegen einer Mischung aus (a) es ist leicht zu umgehen (wie rgettman zeigt), (b) es kompliziert die Sprache, (b.1) es führt knifflige Randfälle ein, von denen einige sogar mit anderen Merkmalen der Sprache unvereinbar sind, (c) die Designer der JLS hatten nicht das Gefühl, dass sie Zeit hatten, daran zu arbeiten.
Jede Änderung der Java-Sprache wird sehr schwierig sein.
In Ihrem Beispiel geht es nicht wirklich um die Notwendigkeit einer Typvariablen. Der Compiler erstellt bereits eine Typvariable durch Wildcard-Capture, nur dass die Typvariable für den Programmierer nicht verfügbar ist. Es könnte mehrere Mittel geben
Lassen Sie den Programmierer auf die Typvariable zugreifen. Dies ist keine leichte Aufgabe. Wir müssen eine bizzare Syntax und Semantik erfinden. Die Seltsamkeit und Komplexität mag den Nutzen nicht rechtfertigen.
Gemeinsame Capture-Konvertierung Momentan wird jeder Ausdruck separat durch Capture Conversion abgearbeitet. Die Spezifikation erkennt nicht, dass foo
foo
ist, sodass die beiden Ausdrücke dieselbe Capture-Konvertierung verwenden sollten. Es ist in einigen einfachen Fällen machbar, zum Beispiel sollte eine effektive finale lokale Variable nur einmal und nicht bei jedem Zugriff Capture-konvertiert werden.
abgeleiteter Variablentyp - var foo2 = foo;
Der Typ von foo2
wird von der rechten Seite abgeleitet, die zuerst die Capture-Konvertierung durchläuft, daher ist der Typ foo2
Foo<x> for some x
, mit einem neuen Typ Variable x (immer noch unbenennbar). Oder verwende var
direkt auf foo
- for(var foo: foos)
. Der abgeleitete Variablentyp ist eine ausgereifte / getestete Technik, und sein Nutzen ist viel breiter als die Lösung dieses Problems. Also werde ich für diesen wählen. Es könnte in Java 9 kommen, wenn wir so lange leben können.
Das Problem ist, dass in der fraglichen Aussage:
%Vor% Die zwei Vorkommen von ?
können (soweit der Compiler weiß) an verschiedene Typen gebunden sein. Es mag albern erscheinen, dass der Compiler nicht herausfinden kann, dass ein ?
(das von getValue()
zurückgegeben wird) nicht dasselbe ist wie das ?
, das in setValue
erwartet wird. Aber in anderen Situationen ist das Ergebnis nicht so eindeutig. Die Sprache erfordert, dass Typparameter gleich benannt werden, wenn sie identisch sein sollen.
Ein guter Workaround ist in der Antwort von rgettman enthalten.
Tags und Links java generics language-design