Java Pass方法作为参数

254 java interface

我正在寻找一种通过引用传递方法的方法.我知道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
}
Run Code Online (Sandbox Code Playgroud)

调用如:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());
Run Code Online (Sandbox Code Playgroud)

Dan*_*ton 223

编辑:从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");
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:正如Pete Kirkham所指出的那样,还有另一种使用访问者的方法.访问者方法更复杂 - 您的节点都需要通过方法访问者acceptVisitor()- 但是如果您需要遍历更复杂的对象图,那么值得检查.

  • 它是访问者模式(将集合的迭代操作与应用于集合的每个成员的函数分开),而不是命令模式(将方法调用的参数封装到对象中).您特别没有封装参数 - 它由访问者模式的迭代部分提供. (7认同)
  • @Mac - 好!这个在没有一流方法的语言中一次又一次地出现,作为模拟它们的事实上的方式,所以值得记住。 (2认同)
  • ... 或者它可以被认为是策略模式。这对你来说可能更有意义。策略封装了一个算法,但接受参数。尽管当“访问”容器的所有叶子时,就像 OP 一样,会想到访问者模式——这是访问者模式的传统用法。不管怎样,你有一个很好的实现,它可以被认为是策略或访问者。 (2认同)

The*_*Hat 65

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

没有lambda表达式:

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

使用lambda表达式:

obj.aMethod(i -> i == 982);
Run Code Online (Sandbox Code Playgroud)

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

Lambda表达式的语法

lambda表达式包含以下内容:

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

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

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
    
    Run Code Online (Sandbox Code Playgroud)
  • 箭头标记, ->

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

    p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
    
    Run Code Online (Sandbox Code Playgroud)

    如果指定单个表达式,则Java运行时将计算表达式,然后返回其值.或者,您可以使用return语句:

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

    return语句不是表达式; 在lambda表达式中,必须将语句括在大括号({})中.但是,您不必在大括号中包含void方法调用.例如,以下是有效的lambda表达式:

    email -> System.out.println(email)
    
    Run Code Online (Sandbox Code Playgroud)

请注意,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
    }
}
Run Code Online (Sandbox Code Playgroud)
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
    }
}
Run Code Online (Sandbox Code Playgroud)
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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Vin*_*ian 25

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

  • "核心反射工具最初是为基于组件的应用程序构建工具而设计的.[...]通常,在运行时不应在正常应用程序中反射访问对象." 项目53:从Effective Java Second Edition中选择反射接口. - 这是Java创作者的思路;-) (19认同)
  • 当有关语言将类型安全作为其最强大的组成部分之一时,类型安全如何不是一个论据?Java是一种强类型语言,强类型输入是您在另一种编译语言中选择它的原因之一. (12认同)
  • 我不明白为什么不.问题是将方法作为参数传递,这是一种非常有效的方法.这也可以包装成任意数量的漂亮图案,使其看起来很好.这是通用的,不需要任何特殊的接口. (11认同)
  • 没有合理使用反射.看到所有的赞成,我感到震惊.反射从未打算用作一般的编程机制; 只有在没有其他清洁解决方案时才使用它. (8认同)
  • 你有没有在JavaScript fg中输入安全性?类型安全不是一个论据. (2认同)

sta*_*ker 18

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

public interface Callable {
  public void call(int param);
}
Run Code Online (Sandbox Code Playgroud)

用该方法实现一个类

class Test implements Callable {
  public void call(int param) {
    System.out.println( param );
  }
}
Run Code Online (Sandbox Code Playgroud)

//这样调用

Callable cmd = new Test();
Run Code Online (Sandbox Code Playgroud)

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

public invoke( Callable callable ) {
  callable.call( 5 );
}
Run Code Online (Sandbox Code Playgroud)

  • 您可能不必制作自己的界面,因为 java 已经为您定义了很多界面:http://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html (3认同)

小智 14

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

R apply(T t);
Run Code Online (Sandbox Code Playgroud)

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

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

void accept(T t);
Run Code Online (Sandbox Code Playgroud)

它看起来像这样:

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);
    } 
}
Run Code Online (Sandbox Code Playgroud)

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

setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize); 
Run Code Online (Sandbox Code Playgroud)

假设您已在同一个类中定义了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);
}
Run Code Online (Sandbox Code Playgroud)


小智 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
}
Run Code Online (Sandbox Code Playgroud)

您随后调用的内容:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());
Run Code Online (Sandbox Code Playgroud)


ζ--*_*ζ-- 12

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

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

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

public interface NewVersionTest{
    String returnAString(Object oIn, String str);
}
Run Code Online (Sandbox Code Playgroud)

从这里开始,方法的名称无关紧要.在接受lambda的情况下,也可以使用方法参考.例如,要在此处使用我们的签名:

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

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

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

这将输出:

Lambda reached!
lambda return
Run Code Online (Sandbox Code Playgroud)

方法参考类似.鉴于:

public class HelperClass{
    public static String testOtherSig(Object o, String s){
        return "real static method";
    }
}
Run Code Online (Sandbox Code Playgroud)

主要:

public static void main(String[] args){
    printOutput(HelperClass::testOtherSig);
}
Run Code Online (Sandbox Code Playgroud)

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

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


Arv*_*ash 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

25
25
15
15
Run Code Online (Sandbox Code Playgroud)

ONLINE DEMO

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


Smi*_*mig 6

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

private Runnable methodName (final int arg){
    return new Runnable(){
       public void run(){
          // do stuff with arg
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用它像:

private void otherMethodName (Runnable arg){
    arg.run();
}
Run Code Online (Sandbox Code Playgroud)


Syl*_*are 6

我没有找到任何关于如何使用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");
  }
}
Run Code Online (Sandbox Code Playgroud)

基本上你有一个Foo带有默认构造函数的对象。A that 将从类型method为 which 的参数中调用。parametrisedMethodFunction<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
Run Code Online (Sandbox Code Playgroud)

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