Java Pass方法作为参数


277

我正在寻找一种通过引用传递方法的方法。我知道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());

现在,我的解决方案是传递一个附加参数,并使用内部的开关盒选择适当的方法。但是,此解决方案无法同时进行代码重用。

另请参
见此

Answers:


233

编辑:从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()方法来了解访问者-但是,如果您需要遍历更复杂的对象图,那么值得研究。


2
@Mac-很好!在没有一流方法的情况下,这种语言反复出现,而不是作为模拟它们的实际方法,因此值得记住。
丹·文顿

7
这是访问者模式(将访问集合的迭代操作与应用于集合的每个成员的函数分开),而不是命令模式(将方法调用的参数封装到对象中)。您没有专门封装参数-它是由访问者模式的迭代部分提供的。
皮特·柯坎

不,如果您将访问与双重调度相结合,则仅需要accept方法。如果您有一个单态的访问者,那么它就是上面的代码。
皮特·柯坎

在Java 8中可能类似于ex.operS(String :: toLowerCase,“ STRING”)。看到好的文章:studytrails.com/java/java8/...
Zon的

Pete Kirkham是正确的:您的代码实现的是Visitor模式,而不是Command模式(这很好,因为这是OP所需要的。)正如Pete所说,您没有封装参数,因此您没有在使用Command -您的Command接口具有一个带有参数的执行。维基百科没有。这对于命令模式的意图至关重要。如第一段所述,“封装所有信息...此信息包括方法名称,拥有该方法的对象和方法参数的值。”
制造商史蒂夫(Steve)2015年

73

在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 Expressions的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);
    }
}

是否需要从接口继承A类?
Serob_b

1
@Serob_b不。除非您希望将其作为方法引用传递(请参阅::运算符),否则A是什么都没有关系。a.changeThing(component)可以更改为所需的任何语句或代码块,只要它返回void。
戴帽子的家伙

29

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


12
我不明白为什么不这样。问题是将方法作为参数传递,这是一种非常有效的方法。也可以将其包装为任意数量的漂亮外观图案,以使其看起来不错。而且它是通用的,不需要任何特殊的接口。
Vinodh Ramasubramanian,2010年

3
您在JavaScript fg中输入了安全性吗?类型安全不是一个参数。
Danubian Sailor

13
当所讨论的语言将类型安全性作为其最强的组成部分之一时,类型安全性又不是一个争论吗?Java是一种强类型化的语言,而强类型化是您选择它而不是另一种编译语言的原因之一。
亚当·帕金

21
“核心反射工具最初是为基于组件的应用程序构建器工具设计的。通常,不应在运行时在常规应用程序中以反射方式访问对象。” 条款53:Effective Java Second Edition中的界面倾向于反射。---那是Java创造者的思路;-)
Wilhem Meignan 2014年

8
不能合理使用反射。看到所有支持我都感到震惊。反射从未打算用作一般的编程机制;仅在没有其他清洁溶液时使用。
制造商史蒂夫(Steve)2015年

22

从Java 8开始,有一个Function<T, R>接口(docs),它具有方法

R apply(T t);

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

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

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

19

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

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

1
您可能不必创建自己的接口,因为Java为您定义了很多接口:docs.oracle.com/javase/8/docs/api/java/util/function/…–
苗条

@slim有趣的一点是,这些定义的稳定性如何?是否打算按照您的建议习惯使用它们,否则可能会破坏它们?
曼努埃尔

@slim实际上,文档回答说:“此程序包中的接口是JDK使用的通用功能接口,也可供用户代码使用。”
曼努埃尔

14

尽管这对于Java 7及以下版本仍然无效,但我相信我们应该放眼未来,至少应认识到Java 8等新版本中的更改

也就是说,此新版本带来了Java的lambda和方法引用(以及新的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,因为它有副作用。但是,它的确以一种更加直观的方式说明了其中一个的使用。


12

上次检查时,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());

6

如果不需要这些方法来返回内容,则可以使它们返回Runnable对象。

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

然后像这样使用它:

private void otherMethodName (Runnable arg){
    arg.run();
}

2

对于如何将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那将被称为从一个参数parametrisedMethod是类型的Function<String, Foo>

  • Function<String, Foo>表示该函数将a String作为参数并返回Foo
  • Foo::Method对应于相同的λx -> 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

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


要在android中使用Apply调用,您需要最低api 24
Ines Belhouchet

1

Java确实具有一种传递名称并对其进行调用的机制。它是反射机制的一部分。您的函数应采用Method类的其他参数。

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}

1

我不是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);
...
}

最后,我在调用initialize方法的同时实现了getSearchText方法。

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })

实际上,这是最好的答案,也是正确的方法。值得更多+1
amdev '19

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()... 声明实现接口的匿名类型。


8
这不是您要寻找的模式。
Pete Kirkham'2

1
观察者模式是关于抽象响应变化的能力。OP希望抽象出集合中每个项目所采取的操作,而不要使用遍历集合的代码(即访客模式)。
Pete Kirkham'2

观察者/侦听器模式实际上与该命令模式相同。他们只是意图不同。当命令代替一等函数/ lambda时,观察者是关于通知的。另一方面,访问者则完全不同。我认为无法用
几句

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

0

我在这里没有找到任何解决方案来说明如何将绑定了参数的方法作为方法的参数来传递。贝娄(Bellow)是如何传递已绑定参数值的方法的示例。

  1. 步骤1:创建两个接口,一个具有返回类型,另一个没有。Java具有相似的接口,但是它们几乎没有实际用途,因为它们不支持Exception throw。


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

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

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());
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.