如何在Java中传递函数作为参数?

在Java中,如何将一个函数作为另一个函数的参数传递?

802787 次浏览

多亏了Java 8,你不需要执行下面的步骤来将函数传递给方法,这就是lambdas的作用,参见Oracle的Lambda表达式教程。这篇文章的其余部分描述了我们过去为了实现这个功能而不得不做的事情。

通常情况下,您将方法声明为带有单个方法的某个接口,然后传入实现该接口的对象。一个例子是common -collections,其中有Closure、Transformer和Predicate的接口,以及传递这些接口实现的方法。Guava是新的改进的公共集合,你可以在那里找到等价的接口。

例如,commons-collections有org。apache。commons。collections。collectionutils,它有很多静态方法,可以接收传入的对象,随机抽取一个,有一个叫exists,带有这个签名

static boolean exists(java.util.Collection collection, Predicate predicate)

它接受一个实现接口Predicate的对象,这意味着它必须有一个接受object并返回布尔值的方法。

所以我可以这样调用它:

CollectionUtils.exists(someCollection, new Predicate() {
public boolean evaluate(Object object) {
return ("a".equals(object.toString());
}
});

并且它返回true或false,取决于someCollection是否包含一个谓词为其返回true的对象。

无论如何,这只是一个例子,而common -collections已经过时了。我只是忘了番石榴的等价物。

Java 8及以上版本

使用Java 8+ lambda表达式,如果你有一个类或接口只有一个抽象方法(有时称为山姆类型),例如:

public interface MyInterface {
String doSomething(int param1, String param2);
}

那么在任何使用MyInterface的地方,你都可以替换一个lambda表达式:

class MyClass {
public MyInterface myInterface = (p1, p2) -> { return p2 + p1; };
}

例如,你可以快速创建一个新线程:

new Thread(() -> someMethod()).start();

并使用方法引用语法使它更干净:

new Thread(this::someMethod).start();

没有 lambda表达式,这最后两个例子看起来像:

new Thread(new Runnable() { someMethod(); }).start();

Java 8之前

一个常见的模式是将它“包装”在一个接口中,例如Callable,然后传入一个Callable:

public T myMethod(Callable<T> func) {
return func.call();
}

这个模式被称为命令模式

请记住,您最好为特定的用途创建一个接口。如果您选择使用callable,那么您可以将上面的T替换为您期望的任何类型的返回值,例如String。

对于你下面的评论,你可以说:

public int methodToPass() {
// do something
}


public void dansMethod(int i, Callable<Integer> myFunc) {
// do something
}

然后调用它,可能使用匿名内部类:

dansMethod(100, new Callable<Integer>() {
public Integer call() {
return methodToPass();
}
});

记住,这不是一个“把戏”。它只是java中基本概念上的函数指针。

您可以使用Java反射来做到这一点。该方法将表示为java.lang.reflect.Method的一个实例。

import java.lang.reflect.Method;


public class Demo {


public static void main(String[] args) throws Exception{
Class[] parameterTypes = new Class[1];
parameterTypes[0] = String.class;
Method method1 = Demo.class.getMethod("method1", parameterTypes);


Demo demo = new Demo();
demo.method2(demo, method1, "Hello World");
}


public void method1(String message) {
System.out.println(message);
}


public void method2(Object object, Method method, String message) throws Exception {
Object[] parameters = new Object[1];
parameters[0] = message;
method.invoke(object, parameters);
}


}

Java(目前)不支持闭包。但还有其他语言,如Scala和Groovy,它们运行在JVM中,并支持闭包。

Java很好地支持闭包。它只是不支持功能,所以你习惯的闭包语法更加笨拙和笨重:你必须用一个方法把所有东西都包装在一个类中。例如,

public Runnable foo(final int x) {
return new Runnable() {
public void run() {
System.out.println(x);
}
};
}

将返回一个Runnable对象,其run()方法“关闭”传入的x,就像在任何支持一级函数和闭包的语言中一样。

我使用的命令模式是@jk。提到,添加一个返回类型:

public interface Callable<I, O> {


public O call(I input);
}
我知道这是一个相当老的帖子,但我有另一个稍微简单的解决方案。 您可以在其中创建另一个类并使其抽象。接下来,创建一个抽象方法,命名为任何你喜欢的名字。在原来的类中创建一个以新类为参数的方法,在这个方法中调用抽象方法。它看起来是这样的

public class Demo {


public Demo(/.../){


}


public void view(Action a){
a.preform();
}


/**
* The Action Class is for making the Demo
* View Custom Code
*/
public abstract class Action {


public Action(/.../){


}


abstract void preform();
}
}

现在您可以执行类似的操作来从类内部调用一个方法。

/...
Demo d = new Demo;
Action a = new Action() {


@Override
void preform() {
//Custom Method Code Goes Here
}
};


/.../
d.view(a)

就像我说的,我知道它很旧,但我认为这样更容易一些。希望能有所帮助。

Lambda表达式

要添加jk。绝妙的回答,现在可以使用< em > Lambda表达式< / em >(在Java 8中)更容易地传递方法。首先,一些背景知识。功能界面是一个具有且仅有一个抽象方法的接口,尽管它可以包含任意数量的默认的方法 (Java 8新增)和静态方法。lambda表达式可以快速实现抽象方法,如果不使用lambda表达式,则无需使用所有不必要的语法。

没有lambda表达式:

obj.aMethod(new AFunctionalInterface() {
@Override
public boolean anotherMethod(int i)
{
return i == 982
}
});

使用lambda表达式:

obj.aMethod(i -> i == 982);

下面是Java Lambda表达式教程的节选:

Lambda表达式的语法

lambda表达式由以下内容组成:

  • 括号内以逗号分隔的形式参数列表。CheckPerson。测试方法包含一个参数p, 它表示Person类的实例。< br > < br > 请注意:你 可以省略lambda表达式中参数的数据类型。在 另外,如果只有一个参数,可以省略括号。 例如,下面的lambda表达式也是有效的:

    p -> p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25
    
  • The arrow token, ->

  • A body, which consists of a single expression or a statement block. This example uses the following expression:

    p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25
    

    如果指定单个表达式,则Java运行时计算表达式,然后返回其值。另外, 你可以使用return语句:

    p -> {
    return p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25;
    }
    

    return语句不是表达式;在lambda表达式中,必须将语句括在大括号({})中。然而,你没有 将void方法调用括在大括号中。例如, 下面是一个有效的lambda表达式:

    email -> System.out.println(email)
    

Note that a lambda expression looks a lot like a method declaration; you can consider lambda expressions as anonymous methods—methods without a name.


Here is how you can "pass a method" using a lambda expression:

Note: this uses a new standard functional interface, java.util.function.IntConsumer.

class A {
public static void methodToPass(int i) {
// do stuff
}
}
import java.util.function.IntConsumer;


class B {
public void dansMethod(int i, IntConsumer aMethod) {
/* you can now call the passed method by saying aMethod.accept(i), and it
will be the equivalent of saying A.methodToPass(i) */
}
}
class C {
B b = new B();


public C() {
b.dansMethod(100, j -> A.methodToPass(j));   //Lambda Expression here
}
}

上面的例子可以使用::运营商来进一步简化。

public C() {
b.dansMethod(100, A::methodToPass);
}