Jak zdefiniować metodę, która przyjmuje parametr lambda jako parametr w Javie 8?

363

W Javie 8 metody mogą być tworzone jako wyrażenia Lambda i mogą być przekazywane przez referencję (z niewielką pracą pod maską). Istnieje wiele przykładów online z lambdami tworzonymi i używanymi z metodami, ale nie ma przykładów, jak zrobić metodę przyjmującą lambda jako parametr. Jaka jest na to składnia?

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


class MyClass{
  //How do I define this method?
  static int method(Lambda l){
    return l(5, 10);
  }
}
Marius
źródło
29
Dobre pytanie. I masz rację: żaden z samouczków nie zawiera tej części.
Martin

Odpowiedzi:

247

Lambda są konstrukcją czysto wywoławczą: odbiorca lambda nie musi wiedzieć, że jest zaangażowana Lambda, zamiast tego przyjmuje interfejs z odpowiednią metodą.

Innymi słowy, definiujesz lub używasz funkcjonalnego interfejsu (tj. Interfejsu z jedną metodą), który akceptuje i zwraca dokładnie to, co chcesz.

Do tego Java 8 dołączony jest zestaw najczęściej używanych typów interfejsów java.util.function(dzięki Maurice Naftalin za podpowiedź na temat JavaDoc).

W tym przypadku konkretnego zastosowania tam java.util.function.IntBinaryOperatorz jednym int applyAsInt(int left, int right)sposobem , więc można napisać swoje methodtak:

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

Ale równie dobrze możesz zdefiniować własny interfejs i używać go w następujący sposób:

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

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

Korzystanie z własnego interfejsu ma tę zaletę, że możesz mieć nazwy, które wyraźniej wskazują zamiar.

Joachim Sauer
źródło
5
Czy będą dostępne wbudowane interfejsy, czy też muszę utworzyć interfejs dla każdej lambdy, którą chcę wziąć?
Marius
Dobrym kompromisem dla dylematu wielokrotnego użytku w stosunku do opisowej nazwy byłoby rozszerzenie wbudowanego interfejsu bez przesłonięcia określonej przez niego metody. To daje opisową nazwę z tylko jednym dodatkowym wierszem kodu.
Will Byrne,
Nie rozumiem On może przekazać lambda na wszystko, a to zadziała? Co się dzieje, gdy przechodzi on (int a, int b, int c)do TwoArgIntOperator. Co się stanie, jeśli TwoArgIntOperatorma dwie metody z tym samym podpisem. Ta odpowiedź jest myląca.
Tomáš Zato - Przywróć Monikę
7
@ TomášZato: jeśli użyjesz lambda z niepasującymi argumentami, kompilator będzie narzekał. Interfejsy z dwiema (nie domyślnymi) metodami nie będą użyteczne jako lambdas, ponieważ można używać tylko interfejsów funkcjonalnych .
Joachim Sauer
Podstawowe pytanie: myślę, że nadal nie rozumiem aspektu przekazywania metody jako parametru z brakującymi parametrami tej metody. Jeśli przekazuje TwoArgIntOperator jako parametr i musi przekazać parametr tej metody osobno, czy to nie wygląda brzydko? Czy istnieje sposób na przekazanie całego elementu wykonawczego wraz z parametrem? Jak w twoim przykładzie, sposób na uniknięcie na stałe „5” i „10”.
instanceOfObject
63

Aby użyć wyrażenia Lambda, musisz utworzyć własny interfejs funkcjonalny lub użyć interfejsu funkcjonalnego Java do operacji, które wymagają dwóch liczb całkowitych i zwracają jako wartość. IntBinaryOperator

Korzystanie z interfejsu funkcjonalnego zdefiniowanego przez użytkownika

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));

    }
}

Korzystanie z funkcjonalnego interfejsu Java

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));

    }
}

Inny przykład, który stworzyłem, jest tutaj

pardeep131085
źródło
Link do IntBinaryOperatordokumentacji jest martwy.
Hendrikto 26.04.16
1
to najlepszy przykład lambda, jaki do tej pory znalazłem, i to był jedyny, który naprawdę kazał mi w końcu go „zdobyć”.
JimmySmithJR
1
Sooooooo ... w zasadzie delegat, ale nie powinniśmy tego tak nazywać?
Ryan Lundy,
37

W przypadku funkcji, które nie mają więcej niż 2 parametry, możesz przekazać je bez definiowania własnego interfejsu. Na przykład,

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));

W BiFunction<Integer, String, List<String>>, Integeri Stringsą jego parametry i List<String>jest jego typ zwracany.

W przypadku funkcji z tylko jednym parametrem można użyć Function<T, R>, gdzie Tjest typem parametru i Rtypem wartości zwracanej. Sprawdź na tej stronie wszystkie interfejsy, które zostały już udostępnione przez Javę.

David Wu
źródło
15

Dostępna jest publicznie dostępna w sieci wersja JavaDocs Java 8 z obsługą Lambda, do której link znajduje się na stronie http://lambdafaq.org/lambda-resources . (To oczywiście powinien być komentarz do odpowiedzi Joachima Sauera, ale nie mogę dostać się na swoje konto SO z punktami reputacji, które muszę dodać komentarz.) Witryna lambdafaq (utrzymuję ją) odpowiada na to i wiele innych Java -lambda pytania.

Uwaga: Ta odpowiedź została napisana, zanim dokumentacja Java 8 GA stała się publicznie dostępna . Zostawiłem jednak na miejscu, ponieważ często zadawane pytania dotyczące Lambda mogą być przydatne dla osób uczących się o funkcjach wprowadzonych w Javie 8.

Maurice Naftalin
źródło
2
Dzięki za link i za utrzymanie tej strony! Pozwoliłem sobie dodać linki do twojego publicznego JavaDoc do mojej odpowiedzi.
Joachim Sauer
1
Na marginesie: Wygląda na to, że budujesz dla Lambdas to, co Angelika Langer zbudowała dla Generics . Dzięki za to Java potrzebuje takich zasobów!
Joachim Sauer
1
Chociaż ten link może odpowiedzieć na pytanie, lepiej dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie. - Z recenzji
ClearLogic
@ClearLogic Tak, uzgodniono. AFAIR Nie chciałem nic dodawać do istniejących odpowiedzi, ale tylko wskazać, gdzie zamieściłem kopię dokumentacji API, która w tamtym czasie nie była łatwo dostępna.
Maurice Naftalin
7

Dla mnie najbardziej sensownym rozwiązaniem jest zdefiniowanie Callbackinterfejsu:

interface Callback {
    void call();
}

a następnie użyć go jako parametru w funkcji, którą chcesz wywołać:

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();
}

Ale tylko precyzja

Lambda nie jest specjalnym interfejsem, klasą ani niczym innym, co mógłbyś sam zadeklarować. Lambdato tylko nazwa nadana () -> {}specjalnej składni, która umożliwia lepszą czytelność przy przekazywaniu interfejsów jednoprocesowych jako parametru. Został zaprojektowany, aby zastąpić to:

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

W powyższym przykładzie nieCallback jest to lambda, to zwykły interfejs; to nazwa składni skrótu, której można użyć do jej wdrożenia.lambda

flawyte
źródło
6

Dla każdego, kto googluje, dobrą metodą byłoby użycie java.util.function.BiConsumer. dawny:

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)));
    }

Nakład wyniósłby: 166

Big_Bad_E
źródło
1
Zamiast tego Consumer<Pair<A,B>>użyj BiConsumer<A,B>w tym przypadku. ( docs )
nobar
Nie wiedziałem, że istnieje, powinienem przesiać pakiet funkcji następnym razem.
Big_Bad_E,
5

Wyrażenie lambda można przekazać jako argument. Aby przekazać wyrażenie lambda jako argument, typ parametru (który odbiera wyrażenie lambda jako argument) musi mieć funkcjonalny typ interfejsu.

Jeśli istnieje funkcjonalny interfejs -

interface IMyFunc {
   boolean test(int num);
}

Istnieje metoda filtrowania, która dodaje liczbę int na liście tylko wtedy, gdy jest ona większa niż 5. Zauważ, że metoda filtrowania ma funkcjonalny interfejs IMyFunc jako jeden z parametrów. W takim przypadku wyrażenie lambda można przekazać jako argument parametru parametru.

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
źródło
2

Można używać interfejsów funkcjonalnych, jak wspomniano powyżej. poniżej znajdują się niektóre przykłady

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());

Mam nadzieję, że to pomoże

Ank
źródło
1

Cóż, to łatwe. Celem wyrażenia lambda jest implementacja interfejsu funkcjonalnego. Jest to interfejs z tylko jedną metodą. Oto niesamowity artykuł na temat predefiniowanych i starszych interfejsów funkcjonalnych.

W każdym razie, jeśli chcesz wdrożyć własny interfejs funkcjonalny, zrób to. Dla prostego przykładu:

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

Stwórzmy więc klasę, w której stworzymy metodę, która akceptuje typ MyFunctionalInterface :

public class Main {

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

    public static void main(String[] args) {

    }
}

Ostatnią rzeczą, którą powinieneś zrobić, to przekazać implementację MyFunctionalInterface do zdefiniowanej przez nas metody:

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");
    }
}

Otóż ​​to!

SanchelliosProg
źródło
1

Lambda to nie obiekt, ale interfejs funkcjonalny. Można zdefiniować tak wiele interfejsów funkcjonalnych, jak to możliwe, używając @FuntionalInterface jako adnotacji

@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));
     }
}

Dane wyjściowe będą następujące

Output : 7

Podstawową koncepcją wyrażenia lambda jest zdefiniowanie własnej logiki, ale już zdefiniowanych argumentów. Tak więc w powyższym kodzie możesz zmienić definicję funkcji do z dodania do dowolnej innej definicji, ale twoje argumenty są ograniczone do 2.

raja emani
źródło
1

Zasadniczo, aby przekazać wyrażenie lamda jako parametr, potrzebujemy typu, w którym możemy go utrzymać. Podobnie jak wartość całkowita, którą przechowujemy w prymitywnej klasie int lub Integer. Java nie ma osobnego typu dla wyrażenia lamda, zamiast tego używa interfejsu jako typu do przechowywania argumentu. Ale ten interfejs powinien być interfejsem funkcjonalnym .

Vamshi
źródło
0

Wykonaj następujące czynności ...

Zadeklarowałeś method(lambda l) Wszystko, co chcesz zrobić, to utworzyć Interfejs z nazwą lambdai zadeklarować jedną metodę abstrakcyjną

public int add(int a,int b);  

nazwa metody nie ma tutaj znaczenia.

Więc kiedy u zadzwoń MyClass.method( (a,b)->a+b) Ta implementacja (a,b)->a+bbędzie wstrzykiwany do metody interfejsu doda .so kiedy nazywasz l.addgo zajmie to wdrożenie i wykonywać dodawanie ai b i return l.add(2,3)powróci 5. - Zasadniczo to robi lambda ..

Arasn
źródło
-1

Oto w przybliżeniu sposób, w jaki C # radzi sobie z tym problemem (ale wyrażony jako kod Java). Coś takiego może zaspokoić prawie wszystkie Twoje potrzeby:

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
źródło
-2

Istnieje elastyczność w stosowaniu lambda jako parametru. Umożliwia programowanie funkcjonalne w Javie. Podstawowa składnia to

param -> method_body

Oto sposób, w jaki możesz zdefiniować metodę przyjmującą interfejs funkcjonalny (używana jest lambda) jako parametr. za. jeśli chcesz zdefiniować metodę zadeklarowaną w interfejsie funkcjonalnym, powiedzmy interfejs funkcjonalny jest podawany jako argument / parametr dla metody wywoływanej zmain()

@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)
        }

    }

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

funcUsesAnonymousOrLambda (fi);

Powyżej widać, jak wyrażenie lambda można zastąpić interfejsem.

Powyżej wyjaśnia szczególne użycie wyrażenia lambda, jest ich więcej. ref Java 8 lambda w lambda nie może modyfikować zmiennej z lambda zewnętrznej

cześć
źródło