如何在Java中将函数作为参数传递?[复制]

How to pass a function as a parameter in Java? [duplicate]

提问人:Jason 提问时间:1/14/2011 最后编辑:Ola StrömJason 更新时间:3/8/2023 访问量:884303

问:

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

Java 方法 参数传递

评论

5赞 Cratylus 1/14/2011
stackoverflow.com/questions/4685435/......
3赞 starcorn 1/14/2011
你可以传递一个 Object,你已经定义了一些你想用作参数的方法
0赞 Sylhare 11/9/2018
有关如何使用 Lambda 函数或将其作为参数传递的更多答案:简单示例参数作为函数 java
0赞 Deqing 1/19/2023
在另一个函数中将 Consumer、Supplier、Predicate 或 Function 定义为参数类型,例如 ,并像 一样传递函数,然后像 一样使用它:Function<String, Boolean> functhis::foofunc.apply(param)

答:

19赞 Nathan Hughes 1/14/2011 #1

多亏了 Java 8,您无需执行以下步骤即可将函数传递给方法,这就是 lambda 的用途,请参阅 Oracle 的 Lambda 表达式教程。这篇文章的其余部分描述了我们过去为了实现此功能而不得不做的事情。

通常,将方法声明为使用单个方法获取某个接口,然后传入实现该接口的对象。一个例子是在 commons-collections 中,其中有 Closure、Transformer 和 Predicate 的接口,以及将这些接口的实现传递到的方法。Guava 是新改进的 commons-collections,您可以在那里找到等效的接口。

例如,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,具体取决于是否包含谓词返回 true 的对象。someCollection

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

评论

0赞 Samuli Pahaoja 10/7/2013
docs.guava-libraries.googlecode.com/git/javadoc/com/google/......是 Guava 的通用单一方法接口。
0赞 Hans Wouters 12/23/2022
从您的答案:)使用 java 14 java.util.function.Function 接口找到解决方案
589赞 jk. 1/14/2011 #2

Java 8 及更高版本

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

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 的基本概念等同于函数指针。

评论

0赞 Jason 1/14/2011
您能否展示将传递给以下内容的确切内容: public void DansMethod(int i, <?function?>){ }
4赞 TomeeNS 1/3/2014
我需要一个更简单的解决方案。谁能帮忙?
36赞 Alexander 5/23/2014
因此,对于我需要执行此操作的 100+ 方法,我需要 100+ 个接口。ggreatttttt =/
3赞 jk. 5/25/2014
@XAleXOwnZX您可以使用单个 Callable<> 接口,但您需要创建 100+ 个 Callable<> 类型的匿名内部类。当然,您现在可以使用 Java 8 lambda 表达式来简化这一点,这在我写这个响应时是不可用的。
2赞 videogameboy76 10/24/2014
@TomeeNS - 这是一个简单的例子,其中 foo() 是作为参数传递的函数,demo() 是将函数作为参数的函数。void demo(final Callable<Void> func){ func.call(); } void foo(){ return null; } demo(new Callable<Void>() {public Void call() {return foo();}}
131赞 bdoughan 1/14/2011 #3

您可以使用 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);
    }

}

评论

10赞 sova 1/14/2011
@Tim Bender:或者也许在“镜子”里面?
1赞 SyntaxT3rr0r 1/14/2011
啊,Java 的特质......没有他们,我们该怎么办?
1赞 AloneInTheDark 3/14/2014
如果你的函数没有参数并且什么都不返回怎么办?
1赞 Daniel 4/21/2015
@AloneInTheDark:如果你看一下文档,你会注意到参数 to 是可选的。此外,方法返回 .__P.S.对于您的特殊情况:建议阅读invokevoidnull
-15赞 Claude 1/14/2011 #4

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

评论

22赞 thejoshwolfe 10/31/2011
Java 从 1.0 版开始支持闭包。您可能在谈论 lambda 表达式或至少是定义的函数,这些函数在语法上没有包含在显式类定义中。
7赞 amalloy 3/12/2011 #5

Java 支持闭包就好了。它只是不支持函数,所以你习惯的闭包语法要笨拙和笨重得多:你必须用一个方法把所有东西都包在一个类中。例如

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

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

5赞 Aram Kocharyan 9/30/2012 #6

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

public interface Callable<I, O> {

    public O call(I input);   
}

评论

1赞 c0der 10/27/2018
与JavaFx Callback<P,R相似>
4赞 Zatran 8/8/2013 #7

我知道这是一个相当古老的帖子,但我有另一个稍微简单的解决方案。 您可以在其中创建另一个类并使其抽象。接下来,创建一个 Abstract 方法,将其命名为您喜欢的任何名称。在原始类中,make 一个将新类作为参数的方法,在此方法中调用 abstract 方法。它看起来像这样。

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)

就像我说的,我知道它很旧,但我认为这种方式更容易一些。希望对你有所帮助。

评论

0赞 Jyro117 8/8/2013
您的解决方案没有提供更简单的方法。使用现有接口来执行此任务比创建自己的自定义抽象类更直观。虽然您的解决方案没有任何问题,并且它确实提供了一种替代方案,但我看不出在接口上使用这种方法的价值或原因。
0赞 Zatran 8/8/2013
从我在接口示例中看到的内容来看,它只允许您使用返回值。如果您想要一个无效的结果,这更有针对性。在某些情况下,这种替代品可能有一些有益的用途。
2赞 Jyro117 8/8/2013
Callable<Void> 适用于这种情况,或者只是使用 Runnable。使用接口更常见,即使您创建了自定义接口,因为它提供了更灵活的层次结构。
105赞 The Guy with The Hat 2/10/2014 #8

Lambda 表达式

为了补充 jk. 的出色答案,您现在可以使用 Lambda 表达式(在 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);

以下是有关 Lambda 表达式的 Java 教程的摘录:

Lambda 表达式的语法

lambda 表达式由以下部分组成:

  • 括在括号中的以逗号分隔的形式参数列表。CheckPerson.test 方法包含一个参数 p, 它表示 Person 类的实例。

    注意:您 可以省略 lambda 表达式中参数的数据类型。在 此外,如果只有一个参数,则可以省略括号。 例如,以下 lambda 表达式也有效:

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
    
  • 箭头令牌,->

  • 正文,由单个表达式或语句块组成。此示例使用以下表达式:

    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)
    

请注意,lambda 表达式看起来很像方法声明; 您可以将 Lambda 表达式视为匿名方法 - 方法 没有名字。


以下是使用 lambda 表达式“传递方法”的方法:

注意:这使用一个新的标准函数接口 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);
}

评论

1赞 Celeritas 7/25/2014
“函数式接口是一个具有且只有一个抽象方法的接口,尽管它可以包含任意数量的默认方法(Java 8 中的新方法)和静态方法”您说,在 Java 8 中,我们现在可以在接口中使用静态方法?
1赞 The Guy with The Hat 7/25/2014
@Celeritas 是的,我在这里详细解释了。
0赞 rupinderjeet 9/13/2016
j是整数吗?
0赞 The Guy with The Hat 9/13/2016
@rupinderjeet47 是的。在课堂上,以一个作为它的论据。AmethodToPass()int