Як визначити метод, який приймає лямбда як параметр у 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
Чи будуть вбудовані інтерфейси, які потрібно використовувати, або я повинен створити інтерфейс для кожної лямбда, яку хочу взяти?
Маріус

Хорошим компромісом щодо дилеми імен для повторного використання та описового імені було б розширення вбудованого інтерфейсу, не змінюючи метод, який він визначає. Це дає вам описову назву лише з одним додатковим рядком коду.
Буде Бірн

Я не розумію. Він може здати лямбда на що завгодно, і це спрацює? Що станеться , якщо він проходить (int a, int b, int c)за TwoArgIntOperator. Що станеться, якщо TwoArgIntOperatorє два способи з однаковою підписом. Ця відповідь бентежить.
Томаш Зато - Відновіть Моніку

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

Основне запитання: я думаю, я все ще не розумію аспект передачі методу як параметра, у якому відсутні параметри цього методу. Якщо він передає TwoArgIntOperator як параметр і йому потрібно передавати параметр цього методу окремо, чи не виглядає це некрасиво? Чи є спосіб передати повний орган виконання разом з параметром? Як у вашому прикладі, спосіб уникнути жорсткого кодування "5" та "10".
instanceOfObject

63

Для використання виразу Lambda потрібно або створити власний функціональний інтерфейс, або використовувати функціональний інтерфейс 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
Sooooooo ... в основному делегат, але ми не повинні так називати?
Райан Лунді

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

В BiFunction<Integer, String, List<String>>, Integerі Stringє його параметрами, і List<String>це його тип повернення.

Для функції, що має лише один параметр, ви можете використовувати Function<T, R>, де Tїї тип параметра, і тип Rйого поверненого значення. На цій сторінці зверніться до всіх інтерфейсів, які вже доступні Java.


15

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

NB Ця відповідь була написана до того, як документація Java 8 GA стала загальнодоступною . Однак я залишив місце, тому що відповіді на питання Ламбда все ще можуть бути корисними людям, які дізнаються про функції, введені в Java 8.


2
Дякуємо за посилання та те, що ви підтримуєте цей сайт! Я взяв на себе сміливість додати посилання на ваш загальнодоступний JavaDoc до своєї відповіді.
Йоахім Зауер

1
В якості примітки: Здається , ви будуєте для лямбда , що Angelika Лангер побудував для генерик . Дякую за це, 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>для цього випадку. ( docs )
nobar

Я не знав, що це існує, я повинен наступного разу пропустити пакет функцій.
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 немає окремого типу для вираження lamda, натомість він використовує інтерфейс як тип для аргументу. Але цей інтерфейс повинен бути функціональним інтерфейсом .


0

Виконайте наступне ..

Ви заявили, що method(lambda l) все, що ви хочете зробити, це створити інтерфейс з ім'ям lambdaі оголосити один абстрактний метод

public int add(int a,int b);  

Назва методу тут не має значення.

Тому , коли і називають MyClass.method( (a,b)->a+b) Ця реалізація (a,b)->a+bзакачуватиметься в ваш метод інтерфейс додавання .so щоразу , коли ви викликаєте 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) -> {повернути 0; };

funcUsesAnonymousOrLambda (fi);

Тут вище видно, як лямбда-вираз можна замінити інтерфейсом.

Вище пояснюється конкретне вживання лямбда-виразу, їх більше. ref Java 8 лямбда в межах лямбда не може змінити змінну від зовнішньої лямбда

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.