Java Pass方法作为参数

时间:2010-02-02 19:20:03

标签: 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
}

调用如:

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

17 个答案:

答案 0 :(得分:209)

编辑:从Java 8开始,lambda expressionsother 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()方法访问者 - 但如果您需要遍历更复杂的对象图,那么值得检查。

答案 1 :(得分:59)

在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
    
  •   
  • 箭头标记->

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

    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表达式视为匿名方法 - 方法   没有名字。


以下是您如何通过方法&#34;使用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);
    }
}

答案 2 :(得分:24)

使用java.lang.reflect.Method对象并致电invoke

答案 3 :(得分:17)

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

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

答案 4 :(得分:12)

虽然这对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));
}

这只是一个简单的接口调用,直到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 methodMethod references can be static, instance, non-static with arbitrary instances, and even constructors。对于构造函数,将使用类似于ClassName::new的东西。

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

答案 5 :(得分:11)

上次我检查时,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 :(得分:8)

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

R apply(T t);

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

在您的示例中,您需要传递一个以Component类型作为输入并且不返回任何内容的函数 - 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);
}

答案 7 :(得分: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();
}

答案 8 :(得分:1)

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

答案 9 :(得分:1)

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

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

答案 10 :(得分:1)

对于如何使用@~/assets/fonts/myfont.woff作为参数函数的简单方法,我没有找到足够明确的示例。这是一个简单的示例:

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>作为参数并返回String
  • Foo对应于Foo::Method之类的lambda。
  • x -> Foo.method(x);被视为parametrisedMethod(Foo::method)
  • x -> parametrisedMethod(Foo.method(x))基本上是.apply("from a method")

然后将返回输出:

parametrisedMethod(Foo.method("from a method"))

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

答案 11 :(得分: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

答案 12 :(得分: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);
...
}

最后,我在调用 initialize 方法时实施 getSearchText 方法。

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

答案 13 :(得分:0)

我不认为lambda是用于此目的的……Java不是一种功能编程语言,因此永远不会,因此,我们不会将方法作为参数传递。话虽这么说,请记住Java是面向对象的,考虑到这一点,我们可以做任何我们想做的事情。第一个想法是简单地传递“包含方法的对象”作为参数。 因此,每当您需要“传递”方法时,只需传递该类的实例即可。请注意,在定义方法时,应将包含该方法的类的实例添加为参数。 这应该可行,但这不是我们想要的,因为除非您可以访问类代码,否则您无法重新定义方法,并且在许多情况下是不可能的。此外,我认为如果有人需要将方法作为参数传递,那是因为该方法的行为应该是动态的。我的意思是,使用您的类的程序员应该能够选择该方法应返回的内容,而不是其类型。对我们来说幸运的是Java有一个美丽而简单的解决方案:抽象类。简而言之,当您知道方法的“签名”但不了解其行为时,将使用抽象类...您可以将方法的名称和类型包装在抽象类中,然后将该类的实例作为方法的参数...等待...与以前不同吗?并且可以有一个抽象类的实例吗?否和否...但是当创建抽象方法时是...您还必须在扩展抽象类的类中重新定义它,并且由于Java的动态绑定,Java将始终(除非您将其声明为静态,私有和其他一些东西)使用它的重新定义版本。这是一个示例...假设我们要对一个数字数组应用一个函数:因此,如果要平方,则输入-输出应如下所示:[1,2,3,4,...]-> [1,4,9,16 ,...](在像haskell这样的函数式编程语言中,由于使用了诸如“ map”等工具,这很容易。)请注意,对数字进行平方没有什么特别的,我们可以应用我们想要的任何函数t。因此,代码应类似于[args],f-> [f(args)]。返回java =>一个函数只是一个方法,所以我们想要的是一个将另一个函数应用于数组的函数。简而言之,我们需要将方法作为参数传递。这就是我的做法==>

1)定义抽象类和方法

public  abstract class Function 
{
    public abstract double f(double x);
}

2)使用APPLY_TO_ARRAY方法定义类

public class ArrayMap 
{
public static double[] apply_to_array(double[] arr, Function fun)
{
    for(int i=0; i<arr.length;i++)
    {
        arr[i]=fun.f(arr[i]);
    }
    return arr;
}
}

3)创建测试类并获得一些乐趣

public class Testclass extends Function
{
    public static void main(String[] args) 
    {
        double[] myarr = {1,2,3,4};
        ArrayMap.apply_to_array(myarr, new Testclass());
        for (double k : myarr)
        {
        System.out.println(k);
        }

    }

    @Override
    public double f(double x) 
    {

        return Math.log(x);
    }   
}

请注意,我们需要传递一个Function类型的对象,并且由于Testclass扩展了Function类,我们可以使用它,所以强制转换是自动的。

答案 14 :(得分: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));

答案 15 :(得分:0)

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

char

答案 16 :(得分: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());
}