Wie definiere ich eine Methode, die in Java 8 ein Lambda als Parameter verwendet?

363

In Java 8 können Methoden als Lambda-Ausdrücke erstellt und als Referenz übergeben werden (mit ein wenig Arbeit unter der Haube). Es gibt viele Beispiele online, bei denen Lambdas erstellt und mit Methoden verwendet werden, aber keine Beispiele dafür, wie eine Methode erstellt wird, bei der ein Lambda als Parameter verwendet wird. Wie lautet die Syntax dafür?

MyClass.method((a, b) -> a+b);


class MyClass{
  //How do I define this method?
  static int method(Lambda l){
    return l(5, 10);
  }
}
Marius
quelle
29
Gute Frage. Und Sie haben Recht: Keines der Tutorials enthält diesen Teil.
Martin

Antworten:

247

Lambdas sind lediglich ein Call-Site-Konstrukt: Der Empfänger des Lambda muss nicht wissen, dass ein Lambda beteiligt ist, sondern akzeptiert eine Schnittstelle mit der entsprechenden Methode.

Mit anderen Worten, Sie definieren oder verwenden eine funktionale Schnittstelle (dh eine Schnittstelle mit einer einzelnen Methode), die genau das akzeptiert und zurückgibt, was Sie möchten.

Zu diesem Zweck wird Java 8 mit einer Reihe häufig verwendeter Schnittstellentypen geliefert java.util.function(danke an Maurice Naftalin für den Hinweis zum JavaDoc).

Für diesen speziellen Anwendungsfall gibt es java.util.function.IntBinaryOperatormit einer einzigen int applyAsInt(int left, int right)Methode , so dass Sie Ihre schreiben könnten methodwie folgt aus :

static int method(IntBinaryOperator op){
    return op.applyAsInt(5, 10);
}

Sie können aber auch Ihre eigene Oberfläche definieren und wie folgt verwenden:

public interface TwoArgIntOperator {
    public int op(int a, int b);
}

//elsewhere:
static int method(TwoArgIntOperator operator) {
    return operator.op(5, 10);
}

Die Verwendung Ihrer eigenen Schnittstelle hat den Vorteil, dass Sie Namen haben können, die die Absicht deutlicher anzeigen.

Joachim Sauer
quelle
5
Werden integrierte Schnittstellen verwendet, oder muss ich für jedes Lambda, das ich verwenden möchte, eine Schnittstelle erstellen?
Marius
Ein guter Kompromiss zwischen dem Dilemma der Wiederverwendbarkeit und des beschreibenden Namens wäre die Erweiterung der integrierten Schnittstelle, ohne die angegebene Methode zu überschreiben. Das gibt Ihnen Ihren beschreibenden Namen mit nur einer zusätzlichen Codezeile.
Will Byrne
Ich verstehe es nicht Er kann Lambda für alles übergeben und es wird funktionieren? Was passiert , wenn er geht (int a, int b, int c)für TwoArgIntOperator. Was passiert , wenn TwoArgIntOperatorhat zwei Methoden mit derselben Signatur. Diese Antwort ist verwirrend.
Tomáš Zato - Wiedereinsetzung Monica
7
@ TomášZato: Wenn Sie ein Lambda mit nicht übereinstimmenden Argumenten verwenden, wird sich der Compiler beschweren. Und Schnittstellen mit zwei (nicht standardmäßigen) Methoden können nicht als Lambdas verwendet werden, da nur funktionale Schnittstellen verwendet werden können.
Joachim Sauer
Eine grundlegende Frage: Ich glaube, ich verstehe den Aspekt der Übergabe einer Methode als Parameter immer noch nicht, wenn die Parameter dieser Methode fehlen. Wenn er TwoArgIntOperator als Parameter übergibt und den Parameter dieser Methode separat übergeben muss, sieht es dann nicht hässlich aus? Gibt es eine Möglichkeit, den gesamten Ausführungskörper zusammen mit dem Parameter zu übergeben? Wie in Ihrem Beispiel eine Möglichkeit, das Hardcodieren von "5" und "10" zu vermeiden.
instanceOfObject
63

Um den Lambda-Ausdruck zu verwenden, müssen Sie entweder Ihre eigene Funktionsschnittstelle erstellen oder die Java-Funktionsschnittstelle für Operationen verwenden, für die zwei Ganzzahlen erforderlich sind und die als Wert zurückgegeben werden. IntBinaryOperator

Benutzerdefinierte Funktionsschnittstelle verwenden

interface TwoArgInterface {

    public int operation(int a, int b);
}

public class MyClass {

    public static void main(String javalatte[]) {
        // this is lambda expression
        TwoArgInterface plusOperation = (a, b) -> a + b;
        System.out.println("Sum of 10,34 : " + plusOperation.operation(10, 34));

    }
}

Verwenden der Java-Funktionsschnittstelle

import java.util.function.IntBinaryOperator;

public class MyClass1 {

    static void main(String javalatte[]) {
        // this is lambda expression
        IntBinaryOperator plusOperation = (a, b) -> a + b;
        System.out.println("Sum of 10,34 : " + plusOperation.applyAsInt(10, 34));

    }
}

Ein anderes Beispiel, das ich erstellt habe, ist hier

pardeep131085
quelle
Der Link zur IntBinaryOperatorDokumentation ist tot.
Hendrikto
1
Dies ist das beste Beispiel für Lambdas, das ich bisher gefunden habe, und es war das einzige, bei dem ich es wirklich endlich "verstanden" habe.
JimmySmithJR
1
Sooooooo ... im Grunde ein Delegierter, aber wir sollen es nicht so nennen?
Ryan Lundy
37

Für Funktionen, die nicht mehr als 2 Parameter haben, können Sie diese übergeben, ohne eine eigene Schnittstelle zu definieren. Zum Beispiel,

class Klass {
  static List<String> foo(Integer a, String b) { ... }
}

class MyClass{

  static List<String> method(BiFunction<Integer, String, List<String>> fn){
    return fn.apply(5, "FooBar");
  }
}

List<String> lStr = MyClass.method((a, b) -> Klass.foo((Integer) a, (String) b));

In BiFunction<Integer, String, List<String>>, Integerund Stringsind die Parameter, und List<String>ist deren Rückgabetyp.

Für eine Funktion mit nur einem Parameter, können Sie verwenden Function<T, R>, wo Tist seine Parameter - Typ und Rist der Rückgabewert Typ. Auf dieser Seite finden Sie alle Schnittstellen, die Java bereits zur Verfügung stellt.

David Wu
quelle
15

Es gibt eine öffentliche, über das Internet zugängliche Version der Lambda-fähigen Java 8 JavaDocs, die über http://lambdafaq.org/lambda-resources verlinkt ist . (Dies sollte natürlich ein Kommentar zu Joachim Sauers Antwort sein, aber ich kann nicht mit den Reputationspunkten, die ich zum Hinzufügen eines Kommentars benötige, in mein SO-Konto gelangen.) Die Lambdafaq-Site (ich pflege sie) beantwortet diese und viele andere Java-Antworten -lambda Fragen.

NB Diese Antwort wurde geschrieben, bevor die Java 8 GA-Dokumentation öffentlich verfügbar wurde . Ich habe es jedoch beibehalten, da die Lambda-FAQ für Benutzer, die sich mit den in Java 8 eingeführten Funktionen vertraut machen, möglicherweise immer noch nützlich sind.

Maurice Naftalin
quelle
2
Vielen Dank für den Link und die Tatsache, dass Sie diese Seite pflegen! Ich habe mir erlaubt, meiner Antwort Links zu Ihrem öffentlichen JavaDoc hinzuzufügen.
Joachim Sauer
1
Als Randnotiz: Es scheint, dass Sie für Lambdas bauen, was Angelika Langer für Generika gebaut hat . Vielen Dank dafür, Java braucht solche Ressourcen!
Joachim Sauer
1
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. - Von der Überprüfung
ClearLogic
@ClearLogic Ja, einverstanden. AFAIR Ich wollte den vorhandenen Antworten nichts hinzufügen, sondern nur darauf hinweisen, wo ich eine Kopie der API-Dokumentation veröffentlicht hatte, die zu diesem Zeitpunkt ansonsten nicht leicht zugänglich war.
Maurice Naftalin
7

Für mich ist die Lösung, die am sinnvollsten ist, eine CallbackSchnittstelle zu definieren :

interface Callback {
    void call();
}

und um es dann als Parameter in der Funktion zu verwenden, die Sie aufrufen möchten:

void somewhereInYourCode() {
    method(() -> {
        // You've passed a lambda!
        // method() is done, do whatever you want here.
    });
}

void method(Callback callback) {
    // Do what you have to do
    // ...

    // Don't forget to notify the caller once you're done
    callback.call();
}

Nur eine Präzision

Ein Lambda ist keine spezielle Schnittstelle, Klasse oder irgendetwas anderes, das Sie selbst deklarieren könnten. Lambdaist nur der Name der () -> {}speziellen Syntax, die eine bessere Lesbarkeit ermöglicht, wenn Schnittstellen mit einer Methode als Parameter übergeben werden. Es wurde entwickelt, um dies zu ersetzen:

method(new Callback() {
    @Override
    public void call() {
        // Classic interface implementation, lot of useless boilerplate code.
        // method() is done, do whatever you want here.
    }
});

Im obigen Beispiel Callbackhandelt es sich also nicht um ein Lambda, sondern nur um eine reguläre Schnittstelle. lambdaist der Name der Verknüpfungssyntax, mit der Sie sie implementieren können.

fehlerhaft
quelle
6

Für jeden, der dies googelt, wäre eine gute Methode zu verwenden java.util.function.BiConsumer. Ex:

Import java.util.function.Consumer
public Class Main {
    public static void runLambda(BiConsumer<Integer, Integer> lambda) {
        lambda.accept(102, 54)
    }

    public static void main(String[] args) {
        runLambda((int1, int2) -> System.out.println(int1 + " + " + int2 + " = " + (int1 + int2)));
    }

Der Aufdruck wäre: 166

Big_Bad_E
quelle
1
Consumer<Pair<A,B>>Verwenden Sie stattdessen BiConsumer<A,B>für diesen Fall. ( docs )
Nobar
Wusste nicht, dass es das gibt, ich sollte das nächste Mal das Funktionspaket durchsehen.
Big_Bad_E
5

Der Lambda-Ausdruck kann als Argument übergeben werden. Um einen Lambda-Ausdruck als Argument zu übergeben, muss der Typ des Parameters (der den Lambda-Ausdruck als Argument empfängt) vom Typ der funktionalen Schnittstelle sein.

Wenn es eine funktionale Schnittstelle gibt -

interface IMyFunc {
   boolean test(int num);
}

Und es gibt eine Filtermethode, die das int nur dann in die Liste einfügt, wenn es größer als 5 ist. Beachten Sie hier, dass die Filtermethode die Funktionsschnittstelle IMyFunc als einen der Parameter hat. In diesem Fall kann der Lambda-Ausdruck als Argument für den Methodenparameter übergeben werden.

public class LambdaDemo {
    public static List<Integer> filter(IMyFunc testNum, List<Integer> listItems) {
        List<Integer> result = new ArrayList<Integer>();
        for(Integer item: listItems) {
            if(testNum.test(item)) {
                result.add(item);
            }
        }
        return result;
    }
    public static void main(String[] args) {
        List<Integer> myList = new ArrayList<Integer>();
        myList.add(1);
        myList.add(4);
        myList.add(6);
        myList.add(7);
        // calling filter method with a lambda expression
        // as one of the param
        Collection<Integer> values = filter(n -> n > 5, myList);

        System.out.println("Filtered values " + values);
    }
}
infoj
quelle
2

Sie können Funktionsschnittstellen wie oben erwähnt verwenden. Nachfolgend einige Beispiele

Function<Integer, Integer> f1 = num->(num*2+1);
System.out.println(f1.apply(10));

Predicate<Integer> f2= num->(num > 10);
System.out.println(f2.test(10));
System.out.println(f2.test(11));

Supplier<Integer> f3= ()-> 100;
System.out.println(f3.get());

Ich hoffe es hilft

ank
quelle
1

Das ist einfach. Der Zweck des Lambda-Ausdrucks besteht darin, eine funktionale Schnittstelle zu implementieren. Es ist die Schnittstelle mit nur einer Methode. Hier ist ein großartiger Artikel über vordefinierte und ältere funktionale Schnittstellen.

Wie auch immer, wenn Sie Ihre eigene funktionale Schnittstelle implementieren möchten, machen Sie es. Nur zum einfachen Beispiel:

public interface MyFunctionalInterface {
    String makeIt(String s);
}

Erstellen wir also eine Klasse, in der wir eine Methode erstellen, die den Typ von MyFunctionalInterface akzeptiert :

public class Main {

    static void printIt(String s, MyFunctionalInterface f) {
        System.out.println(f.makeIt(s));
    }

    public static void main(String[] args) {

    }
}

Als letztes sollten Sie die Implementierung von MyFunctionalInterface an die von uns definierte Methode übergeben:

public class Main {

    static void printIt(String s, MyFunctionalInterface f) {
        System.out.println(f.makeIt(s));
    }

    public static void main(String[] args) {
        printIt("Java", s -> s + " is Awesome");
    }
}

Das ist es!

SanchelliosProg
quelle
1

Lambda ist kein Objekt, sondern eine funktionale Schnittstelle. Man kann so viele funktionale Schnittstellen wie möglich definieren, indem man das @FuntionalInterface als Anmerkung verwendet

@FuntionalInterface
public interface SumLambdaExpression {
     public int do(int a, int b);
}

public class MyClass {
     public static void main(String [] args) {
          SumLambdaExpression s = (a,b)->a+b;
          lambdaArgFunction(s);
     }

     public static void lambdaArgFunction(SumLambdaExpression s) {
          System.out.println("Output : "+s.do(2,5));
     }
}

Die Ausgabe wird wie folgt sein

Output : 7

Das Grundkonzept eines Lambda-Ausdrucks besteht darin, Ihre eigene Logik, aber bereits definierte Argumente zu definieren. Im obigen Code können Sie die Definition der do-Funktion von einer zusätzlichen zu einer anderen Definition ändern, Ihre Argumente sind jedoch auf 2 beschränkt.

Raja Emani
quelle
1

Um einen Lamda-Ausdruck als Parameter zu übergeben, benötigen wir einen Typ, in dem wir ihn halten können. Genau wie ein ganzzahliger Wert halten wir in der primitiven int- oder Integer-Klasse. Java hat keinen separaten Typ für den Lamda-Ausdruck, sondern verwendet eine Schnittstelle als Typ für das Argument. Diese Schnittstelle sollte jedoch eine funktionale Schnittstelle sein .

Vamshi
quelle
0

Mach Folgendes ..

Sie haben deklariert. method(lambda l) Sie möchten lediglich eine Schnittstelle mit dem Namen erstellen lambdaund eine abstrakte Methode deklarieren

public int add(int a,int b);  

Methodenname spielt hier keine Rolle ..

Also , wenn u nennen MyClass.method( (a,b)->a+b) Diese Implementierung (a,b)->a+bwird an Ihre Schnittstelle Add - Methode injiziert werden .So , wenn Sie nennen l.addes diese Umsetzung dauern wird, und die Addition von aund b und return l.add(2,3)kehrt 5. - Grundsätzlich ist es das, was Lambda tut.

Arasn
quelle
-1

Hier erfahren Sie ungefähr, wie C # dieses Problem behandelt (jedoch als Java-Code ausgedrückt). So etwas könnte fast alle Ihre Bedürfnisse erfüllen:

import static org.util.function.Functions.*;

public class Test {

    public static void main(String[] args)
    {
        Test.invoke((a, b) -> a + b);       
    }

    public static void invoke(Func2<Integer, Integer, Integer> func)
    {
        System.out.println(func.apply(5, 6));
    }
}

package org.util.function;

public interface Functions {

    //Actions:
    public interface Action {
        public void apply();
    }

    public interface Action1<T1> {
        public void apply(T1 arg1);
    }

    public interface Action2<T1, T2> {
        public void apply(T1 arg1, T2 arg2);
    }

    public interface Action3<T1, T2, T3> {
        public void apply(T1 arg1, T2 arg2, T3 arg3);
    }

    public interface Action4<T1, T2, T3, T4> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    }

    public interface Action5<T1, T2, T3, T4, T5> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    }

    public interface Action6<T1, T2, T3, T4, T5, T6> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    }

    public interface Action7<T1, T2, T3, T4, T5, T6, T7> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    }

    public interface Action8<T1, T2, T3, T4, T5, T6, T7, T8> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    }

    //Functions:
    public interface Func<TResult> {
        public TResult apply();
    }

    public interface Func1<T1, TResult> {
        public TResult apply(T1 arg1);
    }

    public interface Func2<T1, T2, TResult> {
        public TResult apply(T1 arg1, T2 arg2);
    }

    public interface Func3<T1, T2, T3, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3);
    }

    public interface Func4<T1, T2, T3, T4, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    }

    public interface Func5<T1, T2, T3, T4, T5, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    }

    public interface Func6<T1, T2, T3, T4, T5, T6, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    }

    public interface Func7<T1, T2, T3, T4, T5, T6, T7, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    }

    public interface Func8<T1, T2, T3, T4, T5, T6, T7, T8, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    }
}
craigrs84
quelle
-2

Die Verwendung von Lambda als Parameter ist flexibel. Es ermöglicht die funktionale Programmierung in Java. Die grundlegende Syntax lautet

param -> method_body

Im Folgenden finden Sie eine Möglichkeit, eine Methode zu definieren, bei der die Funktionsschnittstelle (Lambda wird verwendet) als Parameter verwendet wird. ein. Wenn Sie beispielsweise eine in einer Funktionsschnittstelle deklarierte Methode definieren möchten, wird die Funktionsschnittstelle als Argument / Parameter für eine von aufgerufene Methode angegebenmain()

@FunctionalInterface
interface FInterface{
    int callMeLambda(String temp);
}


class ConcreteClass{

    void funcUsesAnonymousOrLambda(FInterface fi){
        System.out.println("===Executing method arg instantiated with Lambda==="));
    }

    public static void main(){
        // calls a method having FInterface as an argument.
        funcUsesAnonymousOrLambda(new FInterface() {

            int callMeLambda(String temp){ //define callMeLambda(){} here..
                return 0;
            }
        }
    }

/***********Can be replaced by Lambda below*********/
        funcUsesAnonymousOrLambda( (x) -> {
            return 0; //(1)
        }

    }

FInterface fi = (x) -> {return 0; };

funcUsesAnonymousOrLambda (fi);

Hier oben ist zu sehen, wie ein Lambda-Ausdruck durch eine Schnittstelle ersetzt werden kann.

Oben erklärt eine bestimmte Verwendung des Lambda-Ausdrucks, es gibt mehr. ref Java 8 Lambda innerhalb eines Lambda kann keine Variable vom äußeren Lambda ändern

hi.nitish
quelle