356

我正在寻找一种通过引用传递方法的方法。我知道 Java 不会将方法作为参数传递,但是,我想找一个替代方案。

有人告诉我,接口是作为参数传递方法的替代方法,但我不明白接口如何通过引用充当方法。如果我理解正确,接口只是一组未定义的抽象方法。我不想发送一个每次都需要定义的接口,因为几个不同的方法可以使用相同的参数调用相同的方法。

我想要完成的是与此类似的事情:

public void setAllComponents(Component[] myComponentArray, Method myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { //recursive call if Container
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } //end if node
        myMethod(leaf);
    } //end looping through components
}

调用如:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
4

17 回答 17

267

编辑:从 Java 8 开始,lambda 表达式是一个很好的解决方案,正如其他 答案所指出的那样。下面的答案是为 Java 7 及更早版本编写的......


看看命令模式

// NOTE: code not tested, but I believe this is valid java...
public class CommandExample 
{
    public interface Command 
    {
        public void execute(Object data);
    }

    public class PrintCommand implements Command 
    {
        public void execute(Object data) 
        {
            System.out.println(data.toString());
        }    
    }

    public static void callCommand(Command command, Object data) 
    {
        command.execute(data);
    }

    public static void main(String... args) 
    {
        callCommand(new PrintCommand(), "hello world");
    }
}

编辑:正如Pete Kirkham 指出的那样,还有另一种使用Visitor的方法。访问者方法涉及更多一些 - 您的节点都需要通过acceptVisitor()方法感知访问者 - 但是如果您需要遍历更复杂的对象图,那么值得研究。

于 2010-02-02T19:28:09.460 回答
100

在 Java 8 中,您现在可以使用Lambda 表达式和方法引用更轻松地传递方法。首先,一些背景知识:函数式接口是一个只有一个抽象方法的接口,尽管它可以包含任意数量的默认方法(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 表达式“传递方法”的方法:

interface I {
    public void myMethod(Component component);
}

class A {
    public void changeColor(Component component) {
        // code here
    }

    public void changeSize(Component component) {
        // code here
    }
}
class B {
    public void setAllComponents(Component[] myComponentArray, I myMethodsInterface) {
        for(Component leaf : myComponentArray) {
            if(leaf instanceof Container) { // recursive call if Container
                Container node = (Container)leaf;
                setAllComponents(node.getComponents(), myMethodInterface);
            } // end if node
            myMethodsInterface.myMethod(leaf);
        } // end looping through components
    }
}
class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), component -> a.changeColor(component));
        b.setAllComponents(this.getComponents(), component -> a.changeSize(component));
    }
}

C通过使用方法引用可以进一步缩短类,如下所示:

class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), a::changeColor);
        b.setAllComponents(this.getComponents(), a::changeSize);
    }
}
于 2014-07-28T22:02:27.043 回答
57

由于 Java 8 有一个Function<T, R>接口(docs),它有方法

R apply(T t);

您可以使用它将函数作为参数传递给其他函数。T 是函数的输入类型,R 是返回类型。

在您的示例中,您需要传递一个将Componenttype 作为输入且不返回任何内容的函数 - Void。在这种情况下Function<T, R>不是最佳选择,因为没有 Void 类型的自动装箱。您正在寻找的接口被称为Consumer<T>docs)方法

void accept(T t);

它看起来像这样:

public void setAllComponents(Component[] myComponentArray, Consumer<Component> myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { 
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } 
        myMethod.accept(leaf);
    } 
}

你可以使用方法引用来调用它:

setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize); 

假设您在同一个类中定义了 changeColor() 和 changeSize() 方法。


如果您的方法碰巧接受多个参数,您可以使用BiFunction<T, U, R>-T 和 U 作为输入参数类型,R 作为返回类型。还有BiConsumer<T, U>(两个参数,没有返回类型)。不幸的是,对于 3 个或更多的输入参数,您必须自己创建一个接口。例如:

public interface Function4<A, B, C, D, R> {

    R apply(A a, B b, C c, D d);
}
于 2017-10-05T14:26:37.940 回答
33

使用java.lang.reflect.Method对象并调用invoke

于 2010-02-02T19:25:54.557 回答
21

首先定义一个接口,使用你想要作为参数传递的方法

public interface Callable {
  public void call(int param);
}

用方法实现一个类

class Test implements Callable {
  public void call(int param) {
    System.out.println( param );
  }
}

// 像这样调用

Callable cmd = new Test();

这允许您将 cmd 作为参数传递并调用接口中定义的方法调用

public invoke( Callable callable ) {
  callable.call( 5 );
}
于 2010-02-02T19:34:26.130 回答
19

虽然这对 Java 7 及以下版本还无效,但我相信我们应该展望未来,至少认识到 Java 8 等新版本中的变化

也就是说,这个新版本为 Java 带来了lambdas和方法引用(以及新的 API,这是解决这个问题的另一个有效解决方案。虽然它们仍然需要一个接口,但不会创建新对象,并且额外的类文件不需要污染输出目录,因为不同由 JVM 处理。

两种风格(lambda 和方法引用)都需要一个接口,该接口可用于使用签名的单个方法:

public interface NewVersionTest{
    String returnAString(Object oIn, String str);
}

从这里开始,方法的名称将不再重要。在接受 lambda 的情况下,方法引用也是如此。例如,在这里使用我们的签名:

public static void printOutput(NewVersionTest t, Object o, String s){
    System.out.println(t.returnAString(o, s));
}

这只是一个简单的接口调用,直到 lambda 1被传递:

public static void main(String[] args){
    printOutput( (Object oIn, String sIn) -> {
        System.out.println("Lambda reached!");
        return "lambda return";
    }
    );
}

这将输出:

Lambda reached!
lambda return

方法参考类似。鉴于:

public class HelperClass{
    public static String testOtherSig(Object o, String s){
        return "real static method";
    }
}

主要:

public static void main(String[] args){
    printOutput(HelperClass::testOtherSig);
}

输出将是real static method方法引用可以是静态的、实例的、具有任意实例的非静态的,甚至是构造函数ClassName::new对于构造函数,将使用类似的东西。

1这不被某些人认为是 lambda,因为它有副作用。然而,它确实说明了以更直观的方式使用一个。

于 2013-10-27T21:53:13.447 回答
13

上次我检查时,Java 无法在本机上做你想做的事;您必须使用“解决方法”来解决这些限制。在我看来,接口是一种选择,但不是一个好的选择。也许谁告诉你的意思是这样的:

public interface ComponentMethod {
  public abstract void PerfromMethod(Container c);
}

public class ChangeColor implements ComponentMethod {
  @Override
  public void PerfromMethod(Container c) {
    // do color change stuff
  }
}

public class ChangeSize implements ComponentMethod {
  @Override
  public void PerfromMethod(Container c) {
    // do color change stuff
  }
}

public void setAllComponents(Component[] myComponentArray, ComponentMethod myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { //recursive call if Container
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } //end if node
        myMethod.PerfromMethod(leaf);
    } //end looping through components
}

然后你会调用它:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
于 2010-02-02T19:38:19.490 回答
10

如果您不需要这些方法返回某些东西,您可以让它们返回 Runnable 对象。

private Runnable methodName (final int arg) {
    return (new Runnable() {
        public void run() {
          // do stuff with arg
        }
    });
}

然后像这样使用它:

private void otherMethodName (Runnable arg){
    arg.run();
}
于 2014-06-12T13:40:33.303 回答
5

java.util.function.Function对于如何将简单方法用作参数函数,我没有找到任何足够明确的示例。这是一个简单的例子:

import java.util.function.Function;

public class Foo {

  private Foo(String parameter) {
    System.out.println("I'm a Foo " + parameter);
  }

  public static Foo method(final String parameter) {
    return new Foo(parameter);
  }

  private static Function parametrisedMethod(Function<String, Foo> function) {
    return function;
  }

  public static void main(String[] args) {
    parametrisedMethod(Foo::method).apply("from a method");
  }
}

基本上你有一个Foo带有默认构造函数的对象。method将作为参数从parametrisedMethodwhich 中调用的 A 类型为Function<String, Foo>

  • Function<String, Foo>表示该函数将 aString作为参数并返回 a Foo
  • Foo::Method对应于像这样的lambdax -> Foo.method(x);
  • parametrisedMethod(Foo::method)可以看作x -> parametrisedMethod(Foo.method(x))
  • .apply("from a method")基本上是要做的parametrisedMethod(Foo.method("from a method"))

然后将在输出中返回:

>> I'm a Foo from a method

该示例应该按原样运行,然后您可以使用不同的类和接口从上述答案中尝试更复杂的东西。

于 2018-11-09T04:37:52.353 回答
3

Java-8 以上

从 Java 8 开始,您可以使用 lambda 表达式提供功能接口(只有一个抽象方法的接口)的抽象方法的实现,并将其作为参数传递给方法。

@FunctionalInterface
interface ArithmeticFunction {
    public int calcualate(int a, int b);
}

public class Main {
    public static void main(String args[]) {
        ArithmeticFunction addition = (a, b) -> a + b;
        ArithmeticFunction subtraction = (a, b) -> a - b;

        int a = 20, b = 5;

        System.out.println(perform(addition, a, b));
        // or
        System.out.println(perform((x, y) -> x + y, a, b));

        System.out.println(perform(subtraction, a, b));
        // or
        System.out.println(perform((x, y) -> x - y, a, b));
    }

    static int perform(ArithmeticFunction function, int a, int b) {
        return function.calcualate(a, b);
    }
}

输出:

25
25
15
15

ONLINE DEMO

从方法参考中了解更多信息。

于 2021-10-10T14:44:31.503 回答
1

Java 确实有一种机制来传递名称并调用它。它是反射机制的一部分。您的函数应该采用类方法的附加参数。

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}
于 2010-02-02T19:47:16.537 回答
0

使用观察者模式(有时也称为监听者模式):

interface ComponentDelegate {
    void doSomething(Component component);
}

public void setAllComponents(Component[] myComponentArray, ComponentDelegate delegate) {
    // ...
    delegate.doSomething(leaf);
}

setAllComponents(this.getComponents(), new ComponentDelegate() {
                                            void doSomething(Component component) {
                                                changeColor(component); // or do directly what you want
                                            }
                                       });

new ComponentDelegate()...声明实现接口的匿名类型。

于 2010-02-02T19:33:33.620 回答
0

这是一个基本示例:

public class TestMethodPassing
{
    private static void println()
    {
        System.out.println("Do println");
    }

    private static void print()
    {
        System.out.print("Do print");
    }

    private static void performTask(BasicFunctionalInterface functionalInterface)
    {
        functionalInterface.performTask();
    }

    @FunctionalInterface
    interface BasicFunctionalInterface
    {
        void performTask();
    }

    public static void main(String[] arguments)
    {
        performTask(TestMethodPassing::println);
        performTask(TestMethodPassing::print);
    }
}

输出:

Do println
Do print
于 2016-08-30T09:49:40.170 回答
0

我不是Java专家,但我可以这样解决您的问题:

@FunctionalInterface
public interface AutoCompleteCallable<T> {
  String call(T model) throws Exception;
}

我在我的特殊接口中定义参数

public <T> void initialize(List<T> entries, AutoCompleteCallable getSearchText) {.......
//call here
String value = getSearchText.call(item);
...
}

最后,我在调用初始化方法时实现了 getSearchText方法。

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })
于 2017-08-11T11:15:36.153 回答
0

我在这里没有找到任何解决方案来展示如何将绑定到它的参数作为方法的参数传递。Bellow 是一个示例,说明如何传递已绑定参数值的方法。

  1. 第 1 步:创建两个接口,一个具有返回类型,另一个没有。Java 也有类似的接口,但是它们没有什么实际用途,因为它们不支持抛出异常。


    public interface Do {
    void run() throws Exception;
    }


    public interface Return {
        R run() throws Exception;
    }

  1. 我们如何使用这两个接口在事务中包装方法调用的示例。请注意,我们使用实际参数传递方法。


    //example - when passed method does not return any value
    public void tx(final Do func) throws Exception {
        connectionScope.beginTransaction();
        try {
            func.run();
            connectionScope.commit();
        } catch (Exception e) {
            connectionScope.rollback();
            throw e;
        } finally {
            connectionScope.close();
        }
    }

    //Invoke code above by 
    tx(() -> api.delete(6));

另一个例子展示了如何传递一个实际返回一些东西的方法



        public  R tx(final Return func) throws Exception {
    R r=null;
    connectionScope.beginTransaction();
    try {
                r=func.run();
                connectionScope.commit();
            } catch (Exception e) {
                connectionScope.rollback();
                throw e;
            } finally {
                connectionScope.close();
            }
        return r;
        }
        //Invoke code above by 
        Object x= tx(() -> api.get(id));

于 2019-02-07T21:58:31.170 回答
0

带有反射的解决方案示例,传递的方法必须是公共的

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Program {
    int i;

    public static void main(String[] args) {
        Program   obj = new Program();    //some object

        try {
            Method method = obj.getClass().getMethod("target");
            repeatMethod( 5, obj, method );
        } 
        catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            System.out.println( e ); 
        }
    }

    static void repeatMethod (int times, Object object, Method method)
        throws IllegalAccessException, InvocationTargetException {

        for (int i=0; i<times; i++)
            method.invoke(object);
    }
    public void target() {                 //public is necessary
        System.out.println("target(): "+ ++i);
    }
}
于 2019-09-22T20:53:04.207 回答
0

我很欣赏上面的答案,但我能够使用下面的方法实现相同的行为;从 Javascript 回调中借来的想法。尽管到目前为止一切都很好(在生产中),但我愿意更正。

这个想法是在签名中使用函数的返回类型,这意味着产量必须是静态的。

下面是一个运行超时进程的函数。

public static void timeoutFunction(String fnReturnVal) {

    Object p = null; // whatever object you need here

    String threadSleeptime = null;

    Config config;

    try {
        config = ConfigReader.getConfigProperties();
        threadSleeptime = config.getThreadSleepTime();

    } catch (Exception e) {
        log.error(e);
        log.error("");
        log.error("Defaulting thread sleep time to 105000 miliseconds.");
        log.error("");
        threadSleeptime = "100000";
    }

    ExecutorService executor = Executors.newCachedThreadPool();
    Callable<Object> task = new Callable<Object>() {
        public Object call() {
            // Do job here using --- fnReturnVal --- and return appropriate value
            return null;
        }
    };
    Future<Object> future = executor.submit(task);

    try {
        p = future.get(Integer.parseInt(threadSleeptime), TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        log.error(e + ". The function timed out after [" + threadSleeptime
                + "] miliseconds before a response was received.");
    } finally {
        // if task has started then don't stop it
        future.cancel(false);
    }
}

private static String returnString() {
    return "hello";
}

public static void main(String[] args) {
    timeoutFunction(returnString());
}
于 2019-12-16T10:13:21.017 回答