Warum ist diese Typrückschluss funktioniert nicht mit dieser Lambda-Ausdruck Szenario?
Ich habe ein seltsames Szenario, in dem Typrückschluss funktioniert nicht, da würde ich erwarten, dass bei der Verwendung eines lambda-Ausdrucks. Hier ist eine Annäherung an meine real-Szenario:
static class Value<T> {
}
@FunctionalInterface
interface Bar<T> {
T apply(Value<T> value); //Change here resolves error
}
static class Foo {
public static <T> T foo(Bar<T> callback) {
}
}
void test() {
Foo.foo(value -> true).booleanValue(); //Compile error here
}
Die compile-Fehler bekomme ich auf der zweiten bis letzten Zeile ist
Die Methode booleanValue() ist nicht definiert für den Typ Objekt
wenn ich warf die lambda Bar<Boolean>
:
Foo.foo((Bar<Boolean>)value -> true).booleanValue();
oder wenn ich ändern Sie die Signatur der Methode von Bar.apply
raw-Typen:
T apply(Value value);
dann das problem verschwindet. Die Art und Weise würde ich erwarten, dass dies funktioniert ist, dass:
Foo.foo
nennen sollte schließen, ein Rückgabetypboolean
value
im lambda sollte geschlossen werden, umValue<Boolean>
.
Warum nicht diese Schlußfolgerung funktioniert wie erwartet und wie kann ich dies ändern API machen, damit es funktioniert, wie erwartet?
- Welche Java-Version Sie verwenden?
- Java 1.8.0_25
- Sie haben einfach ein "type mismatch", weil Boolean ist nicht ein Wert<Boolean>. Wie der compiler sollte in der Lage sein zu wissen, dass T einen Wert<T>? Und natürlich ist es auch nicht wissen, wie es ausgegangen Objekt. Blick auf die Funktion Interface-Methode anwenden. Wert ist eine konkrete Art, die in Zusammenhang mit Ihrem Bar-Schnittstelle.
- Ich denke, das was du beschreibst ist die Grundlage für @fukanchik die Antwort von unten. Das problem dort ist, dass der compiler folgert
Value<Object>
stattValue<Boolean>
das ist, was ich bin nach. Bitte fühlen Sie sich frei zu teilen irgendwelche Ideen auf, wie man es an die Arbeit. - Ich habe ein update gemacht auf meine Antwort, können Sie einen Blick zu haben.
- Was ist, wenn Sie
Boolean.TRUE
statttrue
? - Keine änderung
- Wenn Sie nicht wegwerfen der Rückgabetyp Foo.foo, die compiler nutzen könnte, der Zieltyp abgeleitet Schranken für T...
- Auch ohne das Ziel geben, kann man visuell ableiten, welche den Rückgabetyp und die lambda-parameter geben sollte - was ich so erwartet, richtig?
- Leider Typrückschluss ist nicht Gedankenlesen; es ist constraint solving. Die Frage ist also, Woher weiß der compiler, um seine Einschränkungen aus. Die lambda-Körper wird nicht verwendet, um generieren Einschränkungen (aus guten Gründen.) So, während es ist "offensichtlich", dass Sie meinte, dass T=boolean ist, dass die lambda-returns-boolean ist nicht als Eingang verwendet in Typ-Inferenz. Also Nein, der compiler hat nicht die notwendigen Informationen ableiten, dass T in diesem Fall mit dem return-type ignoriert.
- Wenn der compiler nicht die lambda-Körper zu erzeugen Zwänge, warum dann nicht ändern
Bar
Verwendung einer raw -Value
Art etwas ändern? - Raw-Typen sind viral. So dass die änderung der Signatur von apply (), um ein raw-Typ Ursachen der Inferenz zur Behandlung der lambda als roh-und jetzt, aus der Sicht der Inferenz, T erscheint nicht überall, so gibt es nichts zu folgern.
- Ich bin mir nicht sicher, ob ich verstehe deine Erklärung. Wenn es nichts zu folgern, wie weiß der compiler magisch kommen mit
Boolean
als Typ fürT
wenn es instanziiert die Methode Signatur? Sieht es denn an der lambda-Körper in diesem Fall, oder ist etwas anderes passiert?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Unter der Haube
Über einige versteckte
javac
Funktionen, können wir mehr Informationen bekommen über das, was passiert:Dies ist eine Menge von Informationen, lassen Sie uns es brechen.
phase: Methode Anwendbarkeit phase
aktuelle Werte: der tatsächliche Argumente übergeben
Typ-args: explizite Typ-Argumente
Kandidaten: potenziell anwendbare Methoden
ist-Werte ist
<none>
weil unsere implizit typisierte lambda ist nicht passend zur Anwendbarkeit.Der compiler löst Ihr Aufruf
foo
die einzige Methode namensfoo
imFoo
. Es wurde teilweise instanziiert, umFoo.<Object> foo
(da gab es keine Werte oder Typ-Argumente), aber das kann sich ändern, an der deferred-Inferenz Bühne.instanziiert Signatur: die vollständig instanziiert Unterschrift
foo
. Es ist das Ergebnis von diesem Schritt (zu diesem Zeitpunkt keine mehr geben Rückschluss auf die Signatur vonfoo
).Ziel-Art: der Kontext, der Anruf wird gemacht. Wenn die Methode-Aufruf ist ein Teil einer Aufgabe ist, wird es die linke Seite. Wenn der Methodenaufruf ist selbst Teil eines Methodenaufrufs, es wird der parameter-Typ.
Da Ihre Methode invocation ist freistehend, es gibt keine Gegner-Typ. Da gibt es kein target-Typ, keine weitere Folgerung gemacht werden kann, auf
foo
undT
abgeleitet werdenObject
.Analyse
Den compiler nicht implizit typisierte Lambda-Ausdrücke, die während der Inferenz. Bis zu einem gewissen Grad ist dies sinnvoll. Im Allgemeinen, gegeben
param -> BODY
Sie werden nicht in der Lage zu kompilierenBODY
bis Sie einen Typ fürparam
. Wenn Sie nicht versuchen, zu folgern, die Art fürparam
ausBODY
, es führen könnte, um eine Huhn-und-ei-problem geben. Es ist möglich, dass einige Verbesserungen vorgenommen werden, ist dies in zukünftigen Versionen von Java.Lösungen
Foo.<Boolean> foo(value -> true)
Diese Lösung stellt eine explizite Typ-argument zu
foo
(Hinweis: diewith type-args
Abschnitt unten). Dies ändert die partielle Instanziierung der Methode Signatur(Bar<Boolean>)Boolean
, das ist, was Sie wollen.Foo.foo((Value<Boolean> value) -> true)
Diese Lösung explizit Typen lambda, die es ermöglicht, zielgerichtet auf Anwendbarkeit (Hinweis:
with actuals
unten). Dies ändert die partielle Instanziierung der Methode Signatur(Bar<Boolean>)Boolean
, das ist, was Sie wollen.Foo.foo((Bar<Boolean>) value -> true)
Gleiche wie oben, aber mit einem etwas anderen Geschmack.
Boolean b = Foo.foo(value -> true)
Diese Lösung stellt eine explizite Zielgruppe für Ihre Methode aufrufen (siehe
target-type
unten). Dies ermöglicht es der latente-Instanziierung zu folgern, dass die Typ-parameter werden sollteBoolean
stattObject
(sieheinstantiated signature
unten).Haftungsausschluss
Dies ist das Verhalten, das auftreten. Ich weiß nicht, ob dies ist, was angegeben ist in der JLS. Ich könnte Graben um und sehen, ob ich finden konnte, der genaue Abschnitt, der angibt, dieses Verhalten, aber Typ-Inferenz notation gibt mir Kopfschmerzen.
Diese auch nicht vollständig erklären, warum ändern
Bar
Verwendung einer raw -Value
würde dieses Problem lösen:Für einige Grund, es zu verändern verwenden Sie eine raw -
Value
ermöglicht die verzögerte Instanziierung zu folgern, dassT
istBoolean
. Wenn ich zu spekulieren, würde ich vermuten, dass, wenn der compiler versucht, passen die lambda dieBar<T>
es kann daraus schließen, dassT
istBoolean
durch den Blick auf den Körper des lambda-Ausdrucks. Dies bedeutet, dass meine früheren Analyse ist falsch. Der compiler kann ausführen, Typ-Inferenz, die auf dem Körper des einen lambda-Ausdruck, sondern nur auf den Typ von Variablen, die nur erscheinen in der Rückgabetyp.Ableitung auf lambda-parameter-Typ kann hängt von der lambda-Körper.
Den compiler Gesichter ein harter job und versuche den Sinn der impliziten lambda-Ausdrücke
Den Typ der
value
abgeleitet werden muss, zuerst bevor KAUDERWELSCH kompiliert werden kann, da in der Regel die interpretation von KAUDERWELSCH, hängt von der definition vonvalue
.(In deinem speziellen Fall, KAUDERWELSCH geschieht auf eine einfache Konstante unabhängig von
value
.)Javac muss folgern
Value<T>
ersten für parametervalue
; es gibt keine Einschränkungen in Zusammenhang, daherT=Object
. Dann, lambda-Körpertrue
zusammengestellt und anerkannt als Boolean, kompatibel mitT
.Nachdem Sie die änderung vorgenommen, um die funktionale Schnittstelle, die lambda-parameter-Typ nicht erforderlich Inferenz; T bleibt uninfered. Neben der lambda-Körper zusammengestellt ist, und der Rückgabetyp werden angezeigt boolescher Wert, der eingestellt ist als eine untere Schranke für
T
.Einem anderen Beispiel demonstriert das Problem
T abgeleitet werden
String
; der Körper des lambda-nicht an der Inferenz.In diesem Beispiel javac Verhalten scheint sehr vernünftig zu uns; es wahrscheinlich verhinderte ein Fehler bei der Programmierung.
Sie wollen nicht die Inferenz zu mächtig; wenn alles, was wir schreiben kompiliert irgendwie, verlieren wir das Vertrauen der compiler auf der Suche nach Fehlern für uns.
Gibt es andere Beispiele, wo die lambda scheint der Körper zu bieten eindeutige Einschränkungen, aber der compiler kann diese Informationen verwenden. In Java ist die lambda-parameter-Typen müssen erst beseitigt werden, bevor der Körper betrachtet werden kann. Dies ist eine bewusste Entscheidung. Im Gegensatz dazu, C# ist bereit, zu versuchen, verschiedene parameter-Typen und sehen, welche macht den code kompilieren. Java ist der Auffassung, dass zu riskant.
In jedem Fall, wenn implizite lambda ausfällt, und das passiert ziemlich Häufig, eindeutige Typen für lambda-Parameter, in deinem Fall
(Value<Boolean> value)->true
Den einfachen Weg, dies zu beheben, ist eine Deklaration auf dem Aufruf der Methode zu
foo
:Edit: ich nicht finden können, die spezifische Dokumentation über, warum dies notwendig ist, ziemlich viel wie alle anderen auch. Ich vermutete, es könnte sein, weil der primitiven Typen, aber das war nicht richtig. Unabhängig davon, ist Diese syntax wird als eine Ziel-Typ. Auch Target Type in Lambda-Ausdrücke. Die Gründe entziehen sich mir zwar, ich finde keine Dokumentation darüber gibt, warum diese bestimmten Anwendungsfall erforderlich ist.
Edit 2: fand ich diese Frage:
Generische Typ-Inferenz nicht die Arbeit mit method-chaining?
Sieht es aus wie es ist, weil Sie sind, die die Verkettung der Methoden hier. Nach der JSR-Kommentare verwiesen, die in der akzeptierten Antwort, es war eine bewusste weglassen von Funktionen, da der compiler nicht passieren abgeleitete generische Typ-Informationen zwischen verkettete Methodenaufrufe in beide Richtungen. Als Ergebnis, die gesamte Art ausgelöscht durch die Zeit, es kommt der Aufruf
booleanValue
. Hinzufügen des Ziel-type-in beseitigt dieses Verhalten, indem Sie die Einschränkung manuell, anstatt zuzulassen, dass dem compiler die Entscheidung über die beschriebenen Regeln in JLS §18, die scheint nicht zu erwähnen, das auf allen. Dies ist die einzige info, die ich gefunden habe. Wenn jemand feststellt, dass nichts besser ist, würde ich lieben, es zu sehen.T apply(Value value);
zu haben, eine positive Wirkung, doch es tut.Boolean b = Foo.foo(value -> true);
die syntax, Die Sie beschreiben (Foo.<Boolean> foo(value->true)
) ist bekannt als geben Sie Zeuge.Wie die anderen Antworten, ich bin auch in der Hoffnung, jemand intelligenter wird, kann aufzeigen, warum der compiler nicht in der Lage zu folgern, dass
T
istBoolean
.Einen Weg, um zu helfen out der compiler schon das richtige tun, ohne dass änderungen an Ihrer bestehenden Klassen - /interface-design, ist durch das explizite Deklaration der formalen parameter mit dem Typ in Ihrem lambda-Ausdruck. Also, in diesem Fall, indem Sie ausdrücklich erklärte, dass die Art der
value
parameter istValue<Boolean>
.Foo.foo
call leitet einen Rückgabetyp boolean undvalue
im lambda abgeleitet, umValue<Boolean>
. Ich will nicht zu werfen, da dies ist Teil einer öffentlichen API und erfordern eine Besetzung entsteht ein usability-problem.Ich weiß nicht warum, aber Sie brauchen, um hinzuzufügen, separate return-Typ:
einige smart Typ wohl erklären kann, dass.
value
im lambda-Ausdruck ist geschlossen, umValue<Object>
stattValue<Boolean>
das wird noch Probleme verursachen in meiner realen Szenario, in dem der lambda-Ausdruck Körper enthält mehr code...static class Value<T extends Boolean>, interface Bar<T extends Boolean,R>, public static <T extends Boolean,R> R foo(Bar<T,R> callback)
usw...Problem
Wert ableiten zu geben
Value<Object>
weil Sie interpretiert die lambda falsch. Denken Sie an es, wie Sie sich nennen mit dem lambda direkt die Methode anwenden. Also, was Sie tun, ist:und diese richtig abgeleitet zu:
da haben Sie noch nicht den Typ für den Wert.
Einfache Lösung
Aufruf der lambda-Ausdruck in einem richtigen Weg:
diese wird abgeleitet zu:
(Meine) Lösung Empfohlen
Ihre Lösung sollte man schon ein bisschen mehr klar. Wenn Sie möchten einen Rückruf, dann müssen Sie einen Typ-Wert, der zurückgegeben wird.
Habe ich eine generic Callback-Schnittstelle eine generische Wert-Klasse und ein UsingClass, um zu zeigen, wie es zu benutzen.
Callback-Schnittstelle
Wert Klasse
UsingClass Klasse
TestApp mit Haupt -
value -> true
für die Wert wird abgeleitet werden, umValue<Boolean>
und die Methode der Annahme des lambda-zurückBoolean
. Alle anderen änderungen sind in Ordnung, aber diese brauchen, um im Platz zu bleiben, um die API nutzbar.foo
zu akzeptieren, ein einziges argument, um diese Arbeit zu machen.