如何在Java 8中定义一个接受lambda作为参数的方法?

483
在Java 8中,方法可以被创建为Lambda表达式,并且可以通过引用传递(在幕后需要做一些工作)。网上有很多使用方法和Lambda一起创建和使用的示例,但没有关于如何将Lambda作为参数传递给方法的示例。那么,该怎样编写这样的方法呢?
MyClass.method((a, b) -> a+b);


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

46
好问题。你说得对:没有任何教程包含那一部分。 - Martin
16个回答

322

Lambda表达式仅是一个调用现场的构造: 接收Lambda表达式的对象不需要知道Lambda表达式的存在,而是接受一个具有相应方法的接口。

换句话说,您定义或使用一个函数接口(即只有一个方法的接口),该接口接受并返回您想要的内容。

自Java 8以来,在 java.util.function 中有一组常用的接口类型。

对于这个特定的用例,有一个 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);
}

然后使用lambda表达式作为参数调用该方法:

public static void main(String[] args) {
    TwoArgIntOperator addTwoInts = (a, b) -> a + b;
    int result = method(addTwoInts);
    System.out.println("Result: " + result);
}

使用自己的接口有一个优点,那就是你可以使用更加明确表示意图的名称。


11
是否会有内置接口可供使用,还是我必须为每个想要使用的 Lambda 创建一个接口? - Marius
1
在可重用性与描述性名称之间做出良好的折衷方案是扩展内置接口而不覆盖它指定的方法。这样只需添加一行代码即可获得您的描述性名称。 - Will Byrne
2
我不明白。他可以将lambda传递给任何东西,它都能工作吗?如果他将(int a, int b, int c)传递给TwoArgIntOperator会发生什么?如果TwoArgIntOperator两个具有相同签名的方法会发生什么?这个答案很令人困惑。 - Tomáš Zato
9
如果你使用带有不匹配参数的lambda表达式,编译器将会报错。同时,带有两个(非默认)方法的接口也无法用作lambda表达式,因为只有函数式接口才能被用作lambda表达式。 - Joachim Sauer
1
@instanceOfObject 这个想法是,无论你将该方法/lambda/function 传递给谁,他都知道使用什么参数,但不知道调用哪个函数。最常见的例子就是来自 java.util.stream.* 中的所有 Stream 操作。如果你想传递方法+参数,你基本上有一个无参数函数(从调用者的角度来看),并且可以使用RunnableProducer<T>(取决于是否需要返回值)作为你的函数式接口。 - Paŭlo Ebermann
显示剩余3条评论

74

要使用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文档的链接已失效。 - Hendrikto
1
这是我目前发现的关于lambda最好的示例,而且它是唯一一个让我真正“懂得”它的示例。 - JimmySmithJR
1
所以......基本上是一个委托,但我们不应该这样称呼它? - Ryan Lundy
1
这个回答并没有回答问题,问题是“如何创建一个带有lambda参数的方法”,而不是“如何创建和使用lambda”。 - Clement Cherlin

49

对于没有超过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>> 中,IntegerString 是其参数类型,而 List<String> 是其返回值类型。
对于只有一个参数的函数,您可以使用 Function<T, R>,其中 T 是其参数类型,R 是其返回值类型。请参考此 页面 以查看 Java 已经提供的所有接口。

23
对我来说,最有意义的解决方案是定义一个回调函数接口:
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不是您可以自己声明的特殊接口、类或任何其他东西。 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,它只是一个常规接口;lambda是您可以使用的快捷语法来实现它的名称。


4
我们也可以使用一个Runnable代替上述的接口。 - Bernardo Ramos
1
谢谢,终于找到了我一直在寻找的简单答案!之前我非常怀疑只能使用其他答案中的奇怪类来完成这个任务。 - Tharkius

16

有一个公共的Web可访问版本的Lambda-enabled Java 8 JavaDocs,链接在http://lambdafaq.org/lambda-resources上。(这显然应该是对Joachim Sauer答案的评论,但我无法通过我的SO帐户获得所需的声誉点数来添加评论。) lambdafaq网站(我维护它)回答了这个问题和许多其他Java-lambda问题。

NB: 这个答案是在Java 8 GA文档变为公开可用之前写的。我保留了这个答案,因为Lambda FAQ 对于学习Java 8中引入的功能仍然可能有用。


2
感谢你提供链接并维护该网站!我想要在我的回答中添加你公开的 JavaDoc 的链接,希望您不介意。 - Joachim Sauer
1
顺便提一下:看起来你正在为Lambda构建Angelika Langer已经为泛型构建的东西。感谢你这样做,Java需要这样的资源! - Joachim Sauer
1
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。-【来自审查】 - ClearLogic
@ClearLogic 是的,同意。我记得我并不想在现有答案中添加任何内容,只是想指出我曾经发布过 API 文档的副本,当时它并不容易获取。 - Maurice Naftalin

9
Lambda表达式可以作为参数传递。要将Lambda表达式作为参数传递,接收Lambda表达式参数的参数类型必须是函数式接口类型。
如果存在一个函数式接口 -
interface IMyFunc {
   boolean test(int num);
}

还有一个筛选方法,只有在列表中的整数大于5时才添加。请注意,筛选方法具有IMyFunc函数接口作为其中一个参数。在这种情况下,可以将lambda表达式作为方法参数传递。

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

7

如果您正在搜索此问题,一个好的解决方法是使用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
针对这种情况,可以使用BiConsumer<A,B>代替Consumer<Pair<A,B>>。(文档) - Brent Bradburn
不知道这个存在,下次我应该仔细查看函数包。 - Big_Bad_E

5
你可以像上面提到的那样使用功能接口。 以下是一些例子。
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());

希望能有所帮助。

4
Lambda不是对象,而是函数接口。 我们可以使用@FunctionalInterface注解定义尽可能多的函数接口。
@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

Lambda表达式的基本概念是定义自己的逻辑,但使用已定义的参数。因此,在上面的代码中,您可以将do函数的定义从加法更改为任何其他定义,但您的参数仅限于2个。

@FunctionalInterface注释是可选的,但不是强制性的。这可以防止用户向接口添加另一个抽象方法。 - Rahul

3

基本上来说,要将Lambda表达式作为参数传递,我们需要一个可以容纳它的类型。就像我们在原始的int或Integer类中保存整数值一样。Java没有单独的类型用于Lambda表达式,而是使用接口作为类型来保存参数。但这个接口应该是一个函数式接口


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接