2017-11-30 3 views
3

Angenommen, ich habe ein paar Schnittstellen mit genau einer abstrakten Methode. Nachdem diese Schnittstellen kann ich lambda damit erklären:Lambdas auf bestimmten Schnittstellen beschränken

interface A { 
    int c(); 
} 

interface B { 
    int c(); 
} 

public class Main { 
    public static void main(String... args) { 
     A a =() -> 42; 
     B b =() -> 42; 
    } 
} 

Kurze Frage: Gibt es einen Trick oder Hack mit Schnittstelle A für Lambda-Ausdrücke zu beschränken und die Build auf Versuch fehlschlagen, dies zu tun? Jeder Hinweis, ob schmutzig oder nicht, ist willkommen (mit 'schmutzig' meine ich Hacks auf Kompilierungs-/Bytecode-Ebene - etwas, das Quellen und vorzugsweise öffentliche Verträge nicht beeinflusst).

Lange Geschichte: für einige Schnittstellen Implementierer halte ich die Definition equals/hashCode als Teil des Vertrages. Außerdem erstelle ich equals/hashCode automatisch für sie zur Erstellungszeit.

In diesem Zusammenhang sind Lambdas Unruhestifter. Für normale und anonyme Implementierer der Schnittstelle A kann ich eine .class Datei und Instrument seinen Bytecode zur Erstellungszeit finden. Für Lambdas gibt es eine VM-anonyme Klasse, die zur Laufzeit erzeugt wird. Das Beeinflussen einer solchen Klasse scheint zur Zeit des Aufbaus unmöglich zu sein, daher muss ich solche Gelegenheiten für bestimmte Schnittstellen zumindest verbieten.

+1

Ich habe schon alle Gründe bei "Long story" beschrieben. Ist es nicht detailliert genug? – skapral

+0

Nun, ein Lambda kann nicht 'equals' und' hashCode' haben, da Sie sie überhaupt nicht definieren können. Warum also verbieten Sie, dass diese Schnittstelle als '@ FunctionalInterface' verwendet wird? – Eugene

+0

@Eugene, stellen Sie sich vor, dass die Definition von equals und hashCode Teil des Vertrags ist, der von der Schnittstelle "A" bedient wird. Wie es in seinem Javadoc sagt - "müssen alle Implementoren von Schnittstelle A equals und hashCode definieren, die diesem und jenen Richtlinien folgen ... Werkzeug X kann sie für Sie erzeugen". Und dann kommt jemand mit lambdas und bricht gewaltsam den Vertrag, und Werkzeug X kann nichts dagegen tun. Als ich die Schnittstelle "A" definiert habe, habe ich nicht angenommen, dass es die funktionale Schnittstelle ist. Ich wollte, dass es nur eine Schnittstelle zu einer Methode ist. – skapral

Antwort

2

Bitte nehmen Sie sich einen Blick auf meine Lösung dazu:

package com.example.demo; 

public class LambdaDemo { 

    public static void main(String[] args) { 
     //doesn't compile 
     //LambdaRestrictedInterface x =() -> {}; 
     LambdaRestrictedInterface y = new Test(); 
     y.print(); 
    } 

    private static class Test implements LambdaRestrictedInterface { 
     @Override 
     public void print() { 
      System.out.println("print"); 
     } 
    } 

    public interface MyInterface { 
     void print(); 
    } 

    public interface LambdaRestrictedInterface extends MyInterface { 
     @Override 
     default void print() { 
      //hack prevents lambda instantiating 
     } 
    } 
} 

https://dumpz.org/2708733/

Idee ist übergeordnete Schnittstelle mit Standard-impl

Bearbeiten von Absender außer Kraft zu setzen: Nach einiger Überlegung, ich entschied, diese Antwort zu akzeptieren, (da es meinen Bedürfnissen am besten entsprach und ziemlich billig zu implementieren ist) mit einigen formalen Hinzufügungen. Tatsächlich wurde erkannt, dass die minimale Instrumentierung, die genug ist, um zu verhindern, dass die Schnittstelle als Lambda-Typ verwendet wird, einfach eine Standardimplementierung zu ihrer abstrakten Methode hinzufügt.

+0

Ihr Beispiel gab mir einen guten Hinweis. Theoretisch kann ich die Methode der Schnittstelle "A" einfach so einstellen, dass sie standardmäßig ist und eine Laufzeit-Ausnahme auslöst - das verhindert, dass sie der Lambda-Typ ist. Es wird die Arbeit ziemlich gut machen. Nachteil ist, dass es für Entwickler nicht so transparent ist und den Code der Oberfläche etwas beeinträchtigt, aber ich denke, damit kann ich leben. – skapral

+0

Ja, es ist nur eine Frage der Ansätze in Ihrem Unternehmen/Team angewendet, das heißt, wenn Laufzeit Ausnahme gewünscht ist, dann ist es in Ordnung :) Ich persönlich bevorzuge Kompilierzeitprüfung. –

+0

Natürlich mögen wir alle Typ-Sicherheit und scheitern so schnell wie möglich. Aber das Leben ist hart. Ich frage mich, warum es so schwer war, am Anfang eine Art 'nonFunctionalInterface' zu ​​erfinden. – skapral

2

Von etwas herumspielen, sieht es aus wie das desc Feld des invokedynamic Aufruf enthält die Schnittstelle, die implementiert wird. Zum Beispiel, wenn ich ein einfaches () -> {} Runnable erstellt und übergab es dann durch ASM's Bytecode Outline Plugin, das „ASM-ified“ Aufruf sah aus wie:

mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle...

Also, wenn Sie in der Lage sind, den Build-Zeit-Hack zu tun auf der Aufrufseite (im Gegensatz zur Markierung der Anmerkung selbst als nicht lambda-fähig, was ich nicht glaube, dass Sie tun können), dann sollten Sie in der Lage sein, zuerst eine Reihe von unzulässigen Schnittstellen zu kompilieren, und dann überprüfen Sie die invokedynamic ' s desc gegen diesen Satz.

+0

(Ich stelle das als Community-Wiki auf, da ich noch gar nicht damit gearbeitet habe, und ich gebe nur den Keim einer Antwort.) – yshavit

+0

Danke für die Antwort, ich habe es verstanden. Es scheint praktikabel. Das Kommentieren von Nicht-Lambda-Schnittstellen ist eigentlich kein Problem. Ich muss das Set irgendwie noch bestimmen. – skapral

+0

Sie können Methoden 'hashCode' /' equals' nicht in eine Schnittstelle injizieren. Daher müssten Sie die Lambda-Erstellungsstelle zu einer anderen Bootstrap-Methode umleiten, die die gewünschte Implementierungsklasse erzeugen kann (oder eine bestimmte Superklasse unterstützt, von der sie erbt). Aber es ist machbar. – Holger

Verwandte Themen