Как определить метод, который принимает лямбда в качестве параметра в Java 8?


363

В Java 8 методы могут быть созданы как лямбда-выражения и могут быть переданы по ссылке (с небольшой работой под капотом). Есть много примеров онлайн с лямбдами, которые создаются и используются с методами, но нет примеров того, как сделать метод, принимающий лямбду в качестве параметра. Какой синтаксис для этого?

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


class MyClass{
  //How do I define this method?
  static int method(Lambda l){
    return l(5, 10);
  }
}

29
Хороший вопрос. И вы правы: ни один из уроков не содержит этой части.
Мартин

Ответы:


247

Лямбда - это просто конструкция сайта вызова: получателю лямбды не нужно знать, что участвует лямбда, вместо этого он принимает интерфейс с соответствующим методом.

Другими словами, вы определяете или используете функциональный интерфейс (то есть интерфейс с единственным методом), который принимает и возвращает именно то, что вы хотите.

Для этого Java 8 поставляется с набором часто используемых типов интерфейсов java.util.function(спасибо Морису Нафталину за подсказку о JavaDoc).

Для этого используют конкретный случай есть java.util.function.IntBinaryOperatorс одного int applyAsInt(int left, int right)метода , так что вы можете написать свой , methodкак это:

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

Но вы также можете определить свой собственный интерфейс и использовать его следующим образом:

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

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

Преимущество использования собственного интерфейса заключается в том, что вы можете иметь имена, которые более четко обозначают цель.


5
Будут ли использоваться встроенные интерфейсы, или я должен создать интерфейс для каждой лямбды, которую я хочу использовать?
Marius

Хорошим компромиссом для дилеммы повторного использования и описательного имени было бы расширение встроенного интерфейса без переопределения метода, который он определяет. Это дает вам ваше описательное имя только с одной дополнительной строкой кода.
Уилл Бирн

Я не понимаю Он может передать лямбду за что угодно, и это будет работать? Что произойдет , если он проходит (int a, int b, int c)за TwoArgIntOperator. Что произойдет, если TwoArgIntOperatorесть два метода с одинаковой подписью. Этот ответ сбивает с толку.
Томаш Зато - Восстановить Монику

7
@ TomášZato: если вы используете лямбду с несоответствующими аргументами, компилятор будет жаловаться. И интерфейсы с двумя (не по умолчанию) методами не будут использоваться как лямбды, так как могут использоваться только функциональные интерфейсы .
Йоахим Зауэр

Основной вопрос: я думаю, что я все еще не понимаю аспект передачи метода в качестве параметра с пропущенными параметрами этого метода. Если он передает TwoArgIntOperator в качестве параметра и ему нужно передать параметр этого метода отдельно, разве это не выглядит уродливо? Есть ли способ передать полное тело выполнения вместе с параметром? Как и в вашем примере, способ избежать жесткого кодирования «5» и «10».
instanceOfObject

63

Чтобы использовать лямбда-выражение, вам нужно либо создать свой собственный функциональный интерфейс, либо использовать функциональный интерфейс Java для операций, которые требуют двух целых чисел и возвращают в качестве значения. IntBinaryOperator

Использование пользовательского функционального интерфейса

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

    }
}

Использование функционального интерфейса 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));

    }
}

Другой пример, который я создал, здесь


Ссылка на IntBinaryOperatorдокументацию мертва.
Хендрикто

1
это лучший пример лямбды, которую я нашел до сих пор, и она была единственной, которая действительно заставила меня «получить» ее наконец.
JimmySmithJR

1
Оооооооо ... в основном делегат, но мы не должны это так называть?
Райан Ланди

37

Для функций, которые не имеют более 2 параметров, вы можете передавать их без определения собственного интерфейса. Например,

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>>, Integerи Stringявляются его параметрами, и List<String>является его возвращаемым типом.

Для функции, имеющей только один параметр, вы можете использовать Function<T, R>, где Tее тип параметра и тип Rвозвращаемого значения. Обратитесь к этой странице для всех интерфейсов, которые уже сделаны Java.


15

Существует общедоступная веб-версия JavaDocs с поддержкой Lambda Java 8, ссылка на которую размещена по адресу http://lambdafaq.org/lambda-resources . (Очевидно, это должен быть комментарий к ответу Йоахима Сауэра, но я не могу войти в свою учетную запись SO с баллами репутации, которые мне нужны, чтобы добавить комментарий.) Сайт lambdafaq (я его поддерживаю) отвечает на этот и многие другие Java лямбда вопросов.

NB. Этот ответ был написан до того, как документация по Java 8 GA стала общедоступной . Я оставил его на месте, потому что Lambda FAQ может быть полезен для людей, которые знакомятся с функциями, представленными в Java 8.


2
Спасибо за ссылку и тот факт, что вы поддерживаете этот сайт! Я позволил себе добавить ссылки на ваш общедоступный JavaDoc к моему ответу.
Йоахим Зауэр

1
В качестве примечания: кажется, вы создаете для Lambdas то, что Анжелика Лангер создала для Generics . Спасибо за это, Java нуждается в таких ресурсах!
Йоахим Зауэр

1
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными в случае изменения связанной страницы. - Из обзора
ClearLogic

@ClearLogic Да, согласился. AFAIR Я не хотел добавлять что-либо к существующим ответам, а лишь указать, где я разместил копию документации API, которая в то время была недоступна для других целей.
Морис Нафталин

7

Для меня наиболее разумным решением является определение Callbackинтерфейса:

interface Callback {
    void call();
}

а затем использовать его в качестве параметра в функции, которую вы хотите вызвать:

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

Просто точность, хотя

Лямбда - это не специальный интерфейс, класс или что-то еще, что вы можете объявить самостоятельно. Lambdaэто просто имя, данное () -> {}специальному синтаксису, который обеспечивает лучшую читаемость при передаче в качестве параметра интерфейсов с одним методом. Это было разработано, чтобы заменить это:

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

Так что в приведенном выше примере Callbackэто не лямбда, это просто обычный интерфейс; lambdaимя синтаксиса ярлыка, который вы можете использовать для его реализации.


6

Для любого, кто гуглит это, хорошим методом будет использование java.util.function.BiConsumer. например:

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

Оттиск будет: 166


1
Вместо этого Consumer<Pair<A,B>>используйте BiConsumer<A,B>для этого случая. ( документы )
Нобар

Не знал, что существует, я должен просеять через пакет функций в следующий раз.
Big_Bad_E

5

Лямбда-выражение может быть передано в качестве аргумента. Чтобы передать лямбда-выражение в качестве аргумента, тип параметра (который получает лямбда-выражение в качестве аргумента) должен иметь тип функционального интерфейса.

Если есть функциональный интерфейс -

interface IMyFunc {
   boolean test(int num);
}

И есть метод фильтра, который добавляет int в список, только если он больше 5. Обратите внимание, что метод фильтра имеет функциональный интерфейс IMyFunc в качестве одного из параметров. В этом случае лямбда-выражение может быть передано в качестве аргумента параметра метода.

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

2

Вы можете использовать функциональные интерфейсы, как указано выше. ниже приведены некоторые примеры

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

Надеюсь, поможет


1

Ну, это легко. Целью лямбда-выражения является реализация функционального интерфейса. Это интерфейс только с одним методом. Вот отличная статья о предопределенных и устаревших функциональных интерфейсах.

В любом случае, если вы хотите реализовать свой собственный функциональный интерфейс, сделайте это. Просто для простого примера:

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

Итак, давайте создадим класс, в котором мы создадим метод, который принимает тип MyFunctionalInterface :

public class Main {

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

    public static void main(String[] args) {

    }
}

Последнее, что вы должны сделать, это передать реализацию MyFunctionalInterface методу, который мы определили:

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

Это оно!


1

Лямбда - это не объект, а функциональный интерфейс. Можно определить столько функциональных интерфейсов, сколько они могут, используя @FuntionalInterface в качестве аннотации.

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

Выход будет следующим

Output : 7

Основная концепция лямбда-выражения заключается в определении собственной логики, но уже определенных аргументов. Таким образом, в приведенном выше коде вы можете изменить определение функции do в дополнение к любому другому определению, но ваши аргументы ограничены 2.


1

В основном, чтобы передать выражение lamda в качестве параметра, нам нужен тип, в котором мы можем его хранить. Так же как целочисленное значение мы храним в примитиве int или классе Integer. У Java нет отдельного типа для выражения лямда, вместо этого он использует интерфейс в качестве типа для хранения аргумента. Но этот интерфейс должен быть функциональным интерфейсом .


0

Сделайте следующее ..

Вы объявили method(lambda l) все, что вы хотите сделать, это создать интерфейс с именем lambdaи объявить один абстрактный метод

public int add(int a,int b);  

имя метода здесь не имеет значения ..

Поэтому, когда вы вызываете MyClass.method( (a,b)->a+b) эту реализацию, она (a,b)->a+bбудет внедрена в ваш метод add-интерфейса. Так что, когда бы вы ни вызвали ее, l.addона возьмет эту реализацию и выполнит сложение aи b и return l.add(2,3)вернется 5. - В основном это то, что делает лямбда ..


-1

Вот примерно как C # решает эту проблему (но выражается в виде кода Java). Нечто подобное может удовлетворить почти все ваши потребности:

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

-2

Существует гибкость в использовании лямбда в качестве параметра. Это позволяет функциональное программирование в Java. Основной синтаксис

param -> method_body

Ниже приведен способ определения метода с использованием функционального интерфейса (используется лямбда). а. если вы хотите определить метод, объявленный внутри функционального интерфейса, скажем, функциональный интерфейс задается в качестве аргумента / параметра для метода, вызываемого изmain()

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

Здесь выше можно увидеть, как лямбда-выражение можно заменить интерфейсом.

Выше объясняется конкретное использование лямбда-выражения, есть и другие. ref Java 8 лямбда в лямбда не может изменить переменную из внешнего лямбда

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.