Gibt es eine Möglichkeit, die Typisierung von Lambda-Ausdrücken zu umgehen?

8

Dies ist eine Frage, über die ich mich gewundert habe, seit die Lambdas in Java eingeführt wurden, und inspiriert von eine verwandte Frage , ich dachte, dass ich es hier herauf bringen könnte, um zu sehen, ob es irgendwelche Ideen gibt.

(Randnotizen: Es gibt ein ähnliche Frage für C # , aber ich habe keine für Java gefunden. Die Fragen für Java über" Speichern eines Lambda in einer Variablen "beziehen sich immer auf Fälle, in denen der -Typ der Variablen wurde behoben - genau das versuche ich zu umgehen)

Lambda-Ausdrücke erhalten über Ziel-Inferenz den Typ, den sie benötigen. Dies alles wird vom Compiler erledigt. Zum Beispiel die Funktionen

%Vor%

kann mit demselben Lambda-Ausdruck aufgerufen werden:

%Vor%

Der Ausdruck wird sich einmal als eine Klasse, die die Schnittstelle Function<Integer,Boolean> implementiert, und einmal als eine Klasse, die die Schnittstelle Predicate<Integer> implementiert, manifestieren.

Leider gibt es keine Möglichkeit, den Lambda-Ausdruck mit einem Typ zu speichern, der für beide Funktionen gilt, wie in

GenericLambdaType lambda = x -> true;

Dieser "generische Lambda-Typ" müsste den Typ der Methode kodieren, die durch den gegebenen Lambda-Ausdruck implementiert werden kann. Also in diesem Fall wäre es

(Ljava.lang.Integer)Ljava.lang.Boolean lambda = x -> true;

(basierend auf den Standardsignaturen , für die Illustration). (Dies ist nicht völlig unvernünftig: Die C ++ - Lambda-Ausdrücke tun im Grunde genau das ...)

Gibt es also any , um zu verhindern, dass ein Lambda-Ausdruck in einen bestimmten Typ aufgelöst wird?

Gibt es insbesondere einen Trick oder eine Problemumgehung, die es ermöglicht, dass die oben skizzierten Methoden useF und useP mit demselben Objekt aufgerufen werden, wie in

%Vor%

Das ist unwahrscheinlich, also nehme ich an, die Antwort wird einfach lauten: "Nein", aber: Könnte es irgendeinen Weg geben, eine generische , magische Adaptionsmethode zu schreiben

%Vor%

?

Beachten Sie, dass diese Frage eher aus Neugier besteht. Also suche ich buchstäblich nach jedem Weg, dies zu erreichen (außer für benutzerdefinierte Precompiler oder Bytecode-Manipulation).

Es scheint keine einfachen Problemumgehungen zu geben. Ein naive Versuch, die Typinferenz aufzuschieben, indem der Ausdruck in eine generische Hilfsmethode wie in

eingeschlossen wird %Vor%

schlägt natürlich fehl und besagt, dass " Der Zieltyp dieses Ausdrucks muss eine funktionale Schnittstelle sein " (der Typ kann hier einfach nicht abgeleitet werden). Aber ich habe auch andere Optionen in Erwägung gezogen, wie MethodHandles , brutal deaktiviert Abgüsse oder böse Reflexhacken. Alles scheint unmittelbar nach der Kompilierung verloren zu sein, wo das Lambda in einem anonymen Objekt einer anonymen Klasse versteckt ist, dessen einzige Methode via InvokeVirtual ...

aufgerufen wird     
Marco13 31.03.2016, 14:51
quelle

2 Antworten

2

Ich sehe keine Möglichkeit, einen Lambda-Ausdruck, der zu einem bestimmten funktionalen Schnittstellentyp aufgelöst wird, direkt als äquivalenten funktionalen Schnittstellentyp zu interpretieren. Es gibt keine Superschnittstelle oder einen "generischen Lambda-Typ", die beide funktionalen Schnittstellen erweitern oder erweitern könnten, d. H., Die es erzwingt, dass sie genau einen Parameter genau eines spezifischen Typs annimmt und einen bestimmten Typ zurückgibt.

Sie können jedoch eine Dienstprogrammklasse mit Methoden zum Konvertieren von einem Typ einer funktionalen Schnittstelle in eine andere schreiben.

Diese Dienstprogrammklasse konvertiert Prädikate in Funktionen, die boolesche Werte zurückgeben und umgekehrt. Es umfasst Identitätskonvertierungen, sodass Sie sich keine Gedanken darüber machen müssen, ob Sie die Konvertierungsmethode aufrufen.

%Vor%

Die Eingabefunktionen und Prädikate sind Konsumenten von T , also gibt PECS ? super vor. Sie könnten auch andere Überladungen hinzufügen, die BooleanSupplier s, Supplier<Boolean> oder einen anderen funktionalen Schnittstellentyp benötigen, der boolean oder Boolean zurückgibt.

Dieser Testcode wird kompiliert. Sie können eine Variable eines funktionalen Schnittstellentyps übergeben und in den gewünschten funktionalen Schnittstellentyp konvertieren. Wenn Sie bereits den genauen funktionalen Schnittstellentyp haben, müssen Sie die Konvertierungsmethode nicht aufrufen, aber Sie können, wenn Sie möchten.

%Vor%

Die Ausgabe ist:

%Vor%     
rgettman 31.03.2016 17:05
quelle
2

Nehmen wir an, in einer glänzenden Zukunft (sagen wir Java 10 oder 11) haben wir echte Funktionstypen, die es erlauben, eine Funktion zu spezifizieren, ohne sie zu einem bestimmten konventionellen Java-Typ zu machen (und nicht als Objekt, sondern als Wert) .). Dann haben wir immer noch das Problem, dass die bestehenden Methoden

%Vor%

erwarte ein Java-Objekt, das ein herkömmliches Java interface implementiert und sich wie Java-Objekte verhält, d. h. das Ergebnis von theObject instanceof Function oder theObject instanceof Predicate nicht plötzlich ändert. Dies bedeutet, dass es nicht die generische Funktion ist, die plötzlich die erforderliche Schnittstelle implementiert, wenn sie an eine dieser Methoden übergeben wird, sondern dass eine Art von Capture-Konvertierung stattfindet, die ein Objekt erzeugt, das die erforderliche Zielschnittstelle implementiert, ähnlich wie heute Übergeben Sie einen Lambda-Ausdruck an eine dieser Methoden oder wenn Sie ein Predicate in ein Function mit p::test konvertieren (oder umgekehrt mit f::apply ).

Was nicht passieren wird ist, dass Sie dasselbe Objekt an beide Methoden übergeben. Sie haben nur eine implizite Konvertierung, die zur Kompilierungszeit bestimmt wird und wahrscheinlich genauso wie bei heutigen Lambda-Ausdrücken im Byte-Code explizit gemacht wird.

Eine generische Methode wie convertToRequiredTargetType kann nicht funktionieren, weil sie keine Kenntnisse über den Zieltyp hat. Die einzigen Lösungen, um so etwas zu machen, sind die, die Sie ausgeschlossen haben, Precompiler und Byte-Code-Manipulation. Sie könnten eine Methode erstellen, die einen zusätzlichen Parameter akzeptiert, ein Class -Objekt, das die require interface beschreibt, die an die LambdaMetaFactory delegiert, aber diese Methode müsste alles wiederholen, was der Compiler macht, die funktionale Signatur, den Namen der zu implementierende Methode usw.

Für keinen Vorteil ist das Aufrufen dieser Dienstprogrammmethode wie convertToRequiredTargetType(theObject) (oder tatsächlich convertToRequiredTargetType(theObject, Function.class) ) in keiner Weise einfacher als z. %Code%). Ihr Wunsch, eine solche Methode zu erstellen, führte zu Ihrer seltsamen Aussage "Alles scheint unmittelbar nach der Kompilierung verloren zu gehen, wobei das Lambda in einem anonymen Objekt einer anonymen Klasse versteckt ist", wenn Sie tatsächlich ein Objekt haben, das eine funktionale Schnittstelle mit einer bekannten implementiert Signatur und kann daher so einfach wie theObject::test konvertiert werden (wo die IDE den Methodennamen für Sie vervollständigen kann, wenn Sie vergessen haben) ...

    
Holger 05.04.2016 17:53
quelle

Tags und Links