【问题标题】:Java Pass Method as ParameterJava 传递方法作为参数
【发布时间】:2022-01-22 06:46:28
【问题描述】:

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

【问题讨论】:

标签: java interface method-reference


【解决方案1】:

编辑:从 Java 8 开始,lambda expressions 是一个不错的解决方案,正如 other answers 指出的那样。下面的答案是为 Java 7 及更早版本编写的...


看看command pattern

// 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 points out,还有另一种使用Visitor的方法。访问者方法稍微复杂一点——你的节点都需要使用 acceptVisitor() 方法来了解访问者——但如果你需要遍历更复杂的对象图,那么它值得研究。

【讨论】:

  • @Mac-好!这个在没有一流方法的语言中一次又一次地出现,作为模拟它们的实际方式,所以值得记住。
  • 它是访问者模式(将迭代集合的操作与应用于集合的每个成员的函数分开),而不是命令模式(将方法调用的参数封装到一个对象中)。您特别没有封装参数 - 它由访问者模式的迭代部分提供。
  • 不,如果您将访问与双重分派相结合,则只需要 accept 方法。如果你有一个单态访问者,它就是你上面的代码。
  • 在 Java 8 中可能类似于 ex.operS(String::toLowerCase, "STRING")。看好文章:studytrails.com/java/java8/…
  • ...或者可以将其视为策略模式。这对你来说可能更有意义。 Strategy封装了一个算法,但是接受参数。尽管当像 OP 那样“访问”容器的所有叶子时,想到的是访问者模式——这是访问者模式的传统用法。不管怎样,你有一个很好的实现,它可以被认为是 Strategy 或 Visitor。
【解决方案2】:

在 Java 8 中,您现在可以使用 Lambda Expressions 和方法引用更轻松地传递方法。首先,一些背景知识:功能接口是一个只有一个抽象方法的接口,尽管它可以包含任意数量的default methods(Java 8 中的新方法)和静态方法。如果不使用 lambda 表达式,lambda 表达式可以快速实现抽象方法,而无需所有不必要的语法。

没有 lambda 表达式:

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

使用 lambda 表达式:

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

这是来自the Java tutorial on Lambda Expressions的摘录:

Lambda 表达式的语法

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

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

    笔记: 你 可以省略 lambda 表达式中参数的数据类型。在 另外,如果只有一个参数,可以省略括号。 例如,以下 lambda 表达式也是有效的:

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

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

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

    如果您指定单个表达式,则 Java 运行时会计算该表达式,然后返回它的值。或者, 您可以使用返回语句:

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

Class 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 不。除非您想将其作为方法引用传递(请参阅:: 运算符),否则 A 是什么并不重要。 a.changeThing(component)可以改成任何你想要的语句或代码块,只要返回void即可。
【解决方案3】:

由于 Java 8 有一个 Function&lt;T, R&gt; 接口 (docs),它有方法

R apply(T t);

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

在您的示例中,您需要传递一个函数,该函数将 Component 类型作为输入并且不返回任何内容 - Void。在这种情况下,Function&lt;T, R&gt; 不是最佳选择,因为没有 Void 类型的自动装箱。你要找的接口叫Consumer&lt;T&gt;(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&lt;T, U, R&gt; - T 和 U 是输入参数的类型,R 是返回类型。还有BiConsumer&lt;T, U&gt;(两个参数,没有返回类型)。不幸的是,对于 3 个或更多的输入参数,您必须自己创建一个接口。例如:

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

    R apply(A a, B b, C c, D d);
}

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 我不明白为什么不。问题是将方法作为参数传递,这是一种非常有效的方法。这也可以包裹在任意数量的漂亮图案中,使其看起来不错。而且这是在不需要任何特殊接口的情况下尽可能通用的。
    • 你在 JavaScript f.g. 中输入安全了吗?类型安全不是争论。
    • 当所讨论的语言将类型安全作为其最强大的组件之一时,类型安全怎么能不是争论呢? Java 是一种强类型语言,这种强类型是您选择它而不是另一种编译语言的原因之一。
    • “核心反射设施最初是为基于组件的应用程序构建工具设计的。[...] 通常,在运行时不应在正常应用程序中反射访问对象。” Item 53:Prefer interfaces to reflection,来自 Effective Java Second Edition。 -- 这就是 Java 创造者的思路 ;-)
    • 不合理地使用 reflect。看到所有的赞成票,我感到很震惊。 reflect 从未打算用作通用编程机制;仅在没有其他清洁溶液时使用。
    【解决方案5】:

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

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

    用方法实现一个类

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

    // Invoke like that

    Callable cmd = new Test();
    

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

    public invoke( Callable callable ) {
      callable.call( 5 );
    }
    

    【讨论】:

    • 您可能不必制作自己的界面,因为 java 已经为您定义了很多界面:docs.oracle.com/javase/8/docs/api/java/util/function/…
    • @slim 有趣的一点是,这些定义有多稳定,它们是打算按照您的建议习惯使用,还是有可能被破坏?
    • @slim 实际上,文档回答说:“这个包中的接口是 JDK 使用的通用功能接口,也可供用户代码使用。”
    【解决方案6】:

    虽然这对 Java 7 及以下版本还无效,但我相信我们应该展望未来并至少认识到 the changes 会出现在新版本中,例如 Java 8。

    也就是说,这个新版本带来了 lambdas 和对 Java 的方法引用(以及 new APIs,这是该问题的另一个有效解决方案。虽然它们仍然需要一个接口,但不会创建新对象,并且额外的类文件不需要污染输出目录由于 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));
    }
    

    这只是一个简单的接口调用,直到 lambda1个通过:

    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 methodMethod references can be static, instance, non-static with arbitrary instances, and even constructors。对于构造函数,将使用类似于 ClassName::new 的东西。

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

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

        然后像这样使用它:

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

        【讨论】:

        • 简单地说,明确的方法,所以调用:otherMethodName(methodName(5));
        • 如果您不需要返回类型,我想这可行。否则,您将需要使用上面 Arvid Kumar Avinash 回答的界面。
        【解决方案9】:

        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

        从中了解更多信息Method References.

        【讨论】:

          【解决方案10】:

          关于如何将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&lt;String, Foo&gt;

          • Function&lt;String, Foo&gt; 表示该函数接受一个String 作为参数并返回一个Foo
          • Foo::Method 对应一个 lambda,如 x -&gt; Foo.method(x);
          • parametrisedMethod(Foo::method)可以被视为x -&gt; parametrisedMethod(Foo.method(x))
          • .apply("from a method")基本上就是做parametrisedMethod(Foo.method("from a method"))

          然后将在输出中返回:

          >> I'm a Foo from a method
          

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

          【讨论】:

          【解决方案11】:

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

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

          【讨论】:

            【解决方案12】:

            我在这里没有找到任何解决方案来说明如何将绑定到它的参数的方法作为方法的参数传递。波纹管示例说明了如何传递已绑定参数值的方法。

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

            【讨论】:

            • 为什么要在 Java 问题的解决方案中使用 C# connectionScope 类?
            • 它不是 C#。我创建了自己的使用此类的数据库持久层。名字纯属巧合。我不知道 C# 使用同名的类。
            【解决方案13】:

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

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

            【讨论】:

              【解决方案14】:

              使用 Observer 模式(有时也称为 Listener 模式):

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

              【讨论】:

              • 这不是您正在寻找的模式。
              • 观察者模式是关于抽象响应变化的能力。 OP 希望从迭代集合的代码中抽象出对集合中的每个项目采取的操作,这就是访问者模式。
              • Observer/Listener 模式实际上与命令模式相同。他们只是意图不同。观察者是关于通知的,而命令是一流函数/lambda 的替代品。另一方面,访客是完全不同的东西。我认为这不能用几句话来解释,所以请看一下en.wikipedia.org/wiki/Visitor_pattern
              【解决方案15】:

              这是一个基本示例:

              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
              

              【讨论】:

                【解决方案16】:

                我不是 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(getMessageContactModelList(), new AutoCompleteCallable() {
                          @Override
                          public String call(Object model) throws Exception {
                            return "custom string" + ((xxxModel)model.getTitle());
                          }
                        })
                

                【讨论】:

                • 实际上,这是最好的答案,也是正确的方法。值得更多+1
                【解决方案17】:

                我很欣赏上面的答案,但我能够使用下面的方法实现相同的行为;从 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());
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2017-08-21
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-10-14
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多