Java中的回调函数


177

有没有办法在Java方法中传递回调函数?

我试图模仿的行为是传递给函数的.Net委托。

我见过有人建议创建一个单独的对象,但这似乎过于矫kill过正,但是我知道有时候矫kill过正是做事的唯一方法。


51
这太过分了,因为Java无法正常运行(这应该是个双关语...)。
基思·品森

Answers:


155

如果您说的是.NET匿名委托之类的东西,那么我认为Java的匿名类也可以使用。

public class Main {

    public interface Visitor{
        int doJob(int a, int b);
    }


    public static void main(String[] args) {
        Visitor adder = new Visitor(){
            public int doJob(int a, int b) {
                return a + b;
            }
        };

        Visitor multiplier = new Visitor(){
            public int doJob(int a, int b) {
                return a*b;
            }
        };

        System.out.println(adder.doJob(10, 20));
        System.out.println(multiplier.doJob(10, 20));

    }
}

3
这是自Java 1.0以来的规范方法。
查理·马丁

1
我一直在使用它,它比我想要的要冗长得多,但是它可以工作。
Omar Kooheji 09年

23
@奥马尔,同意。在C#工作了很长时间之后,我又回到Java了,真的很想念lambdas / delegates。快来Java!
德鲁·诺阿克斯

4
@DrewNoakes,好消息是,Java 8具有lambda(主要是)...
Lucas

2
由于您已经使用一种方法定义了Visitor接口,因此可以传递匹配的lambda代替它。
Joey Baruch 2015年

42

从Java 8开始,有lambda和方法引用:

例如,如果您想要一个功能接口,A -> B例如:

import java.util.function.Function;

public MyClass {
    public static String applyFunction(String name, Function<String,String> function){
        return function.apply(name);
    }
}

那么你可以这样称呼它

MyClass.applyFunction("42", str -> "the answer is: " + str);
// returns "the answer is: 42"

您也可以通过类方法。说您有:

@Value // lombok
public class PrefixAppender {
    private String prefix;

    public String addPrefix(String suffix){
        return prefix +":"+suffix;
    }
}

然后,您可以执行以下操作:

PrefixAppender prefixAppender= new PrefixAppender("prefix");
MyClass.applyFunction("some text", prefixAppender::addPrefix);
// returns "prefix:some text"

注意事项

在这里,我使用了功能接口Function<A,B>,但程序包中还有许多其他功能java.util.function。最著名的是

  • Suppliervoid -> A
  • ConsumerA -> void
  • BiConsumer(A,B) -> void
  • FunctionA -> B
  • BiFunction(A,B) -> C

以及许多其他专门研究某些输入/输出类型的工具。然后,如果没有提供所需的接口,则可以创建自己的功能接口,如下所示:

@FunctionalInterface
interface Function3<In1, In2, In3, Out> { // (In1,In2,In3) -> Out
    public Out apply(In1 in1, In2 in2, In3 in3);
}

使用示例:

String computeAnswer(Function3<String, Integer, Integer, String> f){
    return f.apply("6x9=", 6, 9);
}

computeAnswer((question, a, b) -> question + "42");
// "6*9=42"

1
对于CallBacks,最好编写自己的功能接口,因为每种回调类型通常都具有多种语法。
Sri Harsha Chilakapati 2014年

我不确定要了解。如果java.util.function您正在寻找其中的一种课程,那您就很好了。然后,您可以使用I / O的泛型。(?)
Juh_ 2014年

您没听懂我的意思,是关于将使用回调函数的C ++代码转换为Java 8的。在这里,对于每个唯一的函数指针,您都必须在Java中创建一个Functional接口,因为实际中会有更多参数生产代码。
Sri Harsha Chilakapati 2014年

2
我的结论是,在大多数情况下,您需要编写自己的功能接口,因为提供的默认接口java.util.function还不够。
Sri Harsha Chilakapati 2014年

1
我添加了有关现有功能接口以及如何创建新接口的说明
Juh_ '19

28

为简单起见,您可以使用Runnable

private void runCallback(Runnable callback)
{
    // Run callback
    callback.run();
}

用法:

runCallback(new Runnable()
{
    @Override
    public void run()
    {
        // Running callback
    }
});

2
我之所以这样,是因为我不需要创建新的接口或类就可以进行简单的回调。谢谢你的提示!
布鲁斯

1
public interface SimpleCallback { void callback(Object... objects); }这非常简单,如果您也需要传递一些参数,可能会很有用。
Marco C.

@cprcrack无法在run()函数中传递值?
克里斯·谢瓦利尔

16

有点挑剔:

我似乎有人建议创建一个单独的对象,但这似乎太过分了

传递回调包括使用几乎所有的OO语言创建一个单独的对象,因此几乎不能认为它是多余的。您可能要说的是,在Java中,它需要您创建一个单独的类,这比具有显式一流函数或闭包的语言更冗长(且占用更多资源)。但是,匿名类至少可以减少冗长,可以内联使用。


12
是的,这就是我的意思。参加30场左右的活动,您将获得30堂课。
2009年

16

但是我看到有一种最理想的方式是我要寻找的..基本上是从这些答案中得出的,但是我不得不将其操纵为更加冗余和高效的.. 我想每个人都在寻找我的想法

要点:

首先使界面如此简单

public interface myCallback {
    void onSuccess();
    void onError(String err);
}

现在可以在需要处理结果的时候使此回调运行- 更有可能在异步调用后运行,并且您想运行一些依赖于这些重用的东西

// import the Interface class here

public class App {

    public static void main(String[] args) {
        // call your method
        doSomething("list your Params", new myCallback(){
            @Override
            public void onSuccess() {
                // no errors
                System.out.println("Done");
            }

            @Override
            public void onError(String err) {
                // error happen
                System.out.println(err);
            }
        });
    }

    private void doSomething(String param, // some params..
                             myCallback callback) {
        // now call onSuccess whenever you want if results are ready
        if(results_success)
            callback.onSuccess();
        else
            callback.onError(someError);
    }

}

doSomething 该函数需要花费一些时间来向其添加回调以在结果到达时通知您,请将回调接口添加为此方法的参数

希望我的观点是清楚的,享受;)


11

在带有lambda的Java 8中,这非常容易。

public interface Callback {
    void callback();
}

public class Main {
    public static void main(String[] args) {
        methodThatExpectsACallback(() -> System.out.println("I am the callback."));
    }
    private static void methodThatExpectsACallback(Callback callback){
        System.out.println("I am the method.");
        callback.callback();
    }
}

在有争论的情况下会是这样吗?pastebin.com/KFFtXPNA
Nashev'6

1
是的 只要保持正确的lambda语法,任何数量的参数(或不包含任何参数)将起作用。
gk bwg rhb mbreafteahbtehnb

7

我发现使用反射库实现的想法很有趣,并提出了一个我认为效果很好的想法。唯一的缺点是丢失了传递有效参数的编译时间检查。

public class CallBack {
    private String methodName;
    private Object scope;

    public CallBack(Object scope, String methodName) {
        this.methodName = methodName;
        this.scope = scope;
    }

    public Object invoke(Object... parameters) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Method method = scope.getClass().getMethod(methodName, getParameterClasses(parameters));
        return method.invoke(scope, parameters);
    }

    private Class[] getParameterClasses(Object... parameters) {
        Class[] classes = new Class[parameters.length];
        for (int i=0; i < classes.length; i++) {
            classes[i] = parameters[i].getClass();
        }
        return classes;
    }
}

你这样用

public class CallBackTest {
    @Test
    public void testCallBack() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        TestClass testClass = new TestClass();
        CallBack callBack = new CallBack(testClass, "hello");
        callBack.invoke();
        callBack.invoke("Fred");
    }

    public class TestClass {
        public void hello() {
            System.out.println("Hello World");
        }

        public void hello(String name) {
            System.out.println("Hello " + name);
        }
    }
}

看起来有点像过度杀伤(/我鸭子)
Diederik 2012年

取决于项目的使用次数和规模:几类项目的矫kill过正,在大型项目上实用。
2014年

另外,看看@monnoo的答案
Juh_ 2014年

5

方法还不是Java中的一等对象。您不能将函数指针作为回调传递。而是创建一个包含所需方法的对象(通常实现一个接口),然后传递该对象。

已经提出了Java闭包的建议(该闭包将提供您想要的行为),但是在即将发布的Java 7版本中将不包含任何闭包。


1
“在Java中,方法还不是一流的对象” –好的,有方法类[1],您当然可以在其中传递实例。这不是Java所期望的干净,惯用的OO代码,但它可能是合宜的。当然可以考虑。[1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
JonasKölker,2009年

4

当我需要Java中的这种功能时,通常使用Observer模式。它确实意味着一个额外的对象,但是我认为这是一种干净的方法,并且是一种广泛理解的模式,有助于提高代码的可读性。



3

我尝试使用java.lang.reflect实现“回调”,这是一个示例:

package StackOverflowQ443708_JavaCallBackTest;

import java.lang.reflect.*;
import java.util.concurrent.*;

class MyTimer
{
    ExecutorService EXE =
        //Executors.newCachedThreadPool ();
        Executors.newSingleThreadExecutor ();

    public static void PrintLine ()
    {
        System.out.println ("--------------------------------------------------------------------------------");
    }

    public void SetTimer (final int timeout, final Object obj, final String methodName, final Object... args)
    {
        SetTimer (timeout, obj, false, methodName, args);
    }
    public void SetTimer (final int timeout, final Object obj, final boolean isStatic, final String methodName, final Object... args)
    {
        Class<?>[] argTypes = null;
        if (args != null)
        {
            argTypes = new Class<?> [args.length];
            for (int i=0; i<args.length; i++)
            {
                argTypes[i] = args[i].getClass ();
            }
        }

        SetTimer (timeout, obj, isStatic, methodName, argTypes, args);
    }
    public void SetTimer (final int timeout, final Object obj, final String methodName, final Class<?>[] argTypes, final Object... args)
    {
        SetTimer (timeout, obj, false, methodName, argTypes, args);
    }
    public void SetTimer (final int timeout, final Object obj, final boolean isStatic, final String methodName, final Class<?>[] argTypes, final Object... args)
    {
        EXE.execute (
            new Runnable()
            {
                public void run ()
                {
                    Class<?> c;
                    Method method;
                    try
                    {
                        if (isStatic) c = (Class<?>)obj;
                        else c = obj.getClass ();

                        System.out.println ("Wait for " + timeout + " seconds to invoke " + c.getSimpleName () + "::[" + methodName + "]");
                        TimeUnit.SECONDS.sleep (timeout);
                        System.out.println ();
                        System.out.println ("invoking " + c.getSimpleName () + "::[" + methodName + "]...");
                        PrintLine ();
                        method = c.getDeclaredMethod (methodName, argTypes);
                        method.invoke (obj, args);
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                    finally
                    {
                        PrintLine ();
                    }
                }
            }
        );
    }
    public void ShutdownTimer ()
    {
        EXE.shutdown ();
    }
}

public class CallBackTest
{
    public void onUserTimeout ()
    {
        System.out.println ("onUserTimeout");
    }
    public void onTestEnd ()
    {
        System.out.println ("onTestEnd");
    }
    public void NullParameterTest (String sParam, int iParam)
    {
        System.out.println ("NullParameterTest: String parameter=" + sParam + ", int parameter=" + iParam);
    }
    public static void main (String[] args)
    {
        CallBackTest test = new CallBackTest ();
        MyTimer timer = new MyTimer ();

        timer.SetTimer ((int)(Math.random ()*10), test, "onUserTimeout");
        timer.SetTimer ((int)(Math.random ()*10), test, "onTestEnd");
        timer.SetTimer ((int)(Math.random ()*10), test, "A-Method-Which-Is-Not-Exists");    // java.lang.NoSuchMethodException

        timer.SetTimer ((int)(Math.random ()*10), System.out, "println", "this is an argument of System.out.println() which is called by timer");
        timer.SetTimer ((int)(Math.random ()*10), System.class, true, "currentTimeMillis");
        timer.SetTimer ((int)(Math.random ()*10), System.class, true, "currentTimeMillis", "Should-Not-Pass-Arguments");    // java.lang.NoSuchMethodException

        timer.SetTimer ((int)(Math.random ()*10), String.class, true, "format", "%d %X", 100, 200); // java.lang.NoSuchMethodException
        timer.SetTimer ((int)(Math.random ()*10), String.class, true, "format", "%d %X", new Object[]{100, 200});

        timer.SetTimer ((int)(Math.random ()*10), test, "NullParameterTest", new Class<?>[]{String.class, int.class}, null, 888);

        timer.ShutdownTimer ();
    }
}

如何将null作为arg传递?
TWiStErRob

@TWiStErRob,在此示例中,就像timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});。输出将是null test: [null]
LiuYan刘研

NPE不会吗 args[i].getClass()吗?我的观点是,如果您基于参数类型选择方法,那么它将不起作用。它可以使用String.format,但不能与其他可接受的东西一起使用null
TWiStErRob

@TWiStErRob,好点!我添加了一个可以手动传递argTypes数组的函数,因此现在我们可以传递null参数/参数而不会发生NullPointerException。样本输出:NullParameterTest: String parameter=null, int parameter=888
LiuYan刘研

3

您还可以Callback使用Delegate模式:

回调.java

public interface Callback {
    void onItemSelected(int position);
}

PagerActivity.java

public class PagerActivity implements Callback {

    CustomPagerAdapter mPagerAdapter;

    public PagerActivity() {
        mPagerAdapter = new CustomPagerAdapter(this);
    }

    @Override
    public void onItemSelected(int position) {
        // Do something
        System.out.println("Item " + postion + " selected")
    }
}

CustomPagerAdapter.java

public class CustomPagerAdapter {
    private static final int DEFAULT_POSITION = 1;
    public CustomPagerAdapter(Callback callback) {
        callback.onItemSelected(DEFAULT_POSITION);
    }
}

1

我最近开始做这样的事情:

public class Main {
    @FunctionalInterface
    public interface NotDotNetDelegate {
        int doSomething(int a, int b);
    }

    public static void main(String[] args) {
        // in java 8 (lambdas):
        System.out.println(functionThatTakesDelegate((a, b) -> {return a*b;} , 10, 20));

    }

    public static int functionThatTakesDelegate(NotDotNetDelegate del, int a, int b) {
        // ...
        return del.doSomething(a, b);
    }
}

1

它有点旧,但是尽管如此...我发现Peter Wilkinson的回答很好,除了它不适用于int / Integer之类的原始类型。问题是.getClass()for的parameters[i],例如返回java.lang.Integer,而另一方面,它不会被正确解释。getMethod(methodName,parameters[])(Java的错误)。

我将其与丹尼尔·斯皮沃克(Daniel Spiewak)的建议结合在一起(在他对此的回答中);成功的步骤包括:捕获NoSuchMethodException-> getMethods()->通过->寻找匹配的对象method.getName(),然后显式循环遍历参数列表并应用Daniels解决方案,例如识别类型匹配和签名匹配。


0

我认为使用抽象类更优雅,如下所示:

// Something.java

public abstract class Something {   
    public abstract void test();        
    public void usingCallback() {
        System.out.println("This is before callback method");
        test();
        System.out.println("This is after callback method");
    }
}

// CallbackTest.java

public class CallbackTest extends Something {
    @Override
    public void test() {
        System.out.println("This is inside CallbackTest!");
    }

    public static void main(String[] args) {
        CallbackTest myTest = new CallbackTest();
        myTest.usingCallback();
    }    
}

/*
Output:
This is before callback method
This is inside CallbackTest!
This is after callback method
*/

这是多态性,而不是回调。您需要检查回调模式。
simo.3792

是的,我正在使用多态从超类回调。您不能仅仅通过多态性来获得此功能。我正在使用Something类中的usingCallback()进行调用,然后它返回到用户已编写其函数test()的子类。
avatar1337

2
回调的目的是使一个事件在发生重大事件时object“通知”另一个object事件,因此第二个object事件可以处理该事件。您abstract class永远不会分开object,只会分开class。您仅使用一种方法就object变得有所不同,classes以执行不同的功能,这是多态性的本质,而不是回调模式。
simo.3792

0
public class HelloWorldAnonymousClasses {

    //this is an interface with only one method
    interface HelloWorld {
        public void printSomething(String something);
    }

    //this is a simple function called from main()
    public void sayHello() {

    //this is an object with interface reference followed by the definition of the interface itself

        new HelloWorld() {
            public void printSomething(String something) {
                System.out.println("Hello " + something);
            }
        }.printSomething("Abhi");

     //imagine this as an object which is calling the function'printSomething()"
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
                new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }
}
//Output is "Hello Abhi"

基本上,如果您要创建接口的对象,则不可能,因为接口不能包含对象。

选项是让某些类实现接口,然后使用该类的对象调用该函数。但是这种方法确实很冗长。

另外,编写新的HelloWorld()(*确保此接口不是类),然后再定义接口方法本身。(*这个定义实际上是匿名类)。然后,您将获得对象引用,通过该对象引用可以调用方法本身。


0

创建一个接口,并在回调类中创建相同的接口属性。

interface dataFetchDelegate {
    void didFetchdata(String data);
}
//callback class
public class BackendManager{
   public dataFetchDelegate Delegate;

   public void getData() {
       //Do something, Http calls/ Any other work
       Delegate.didFetchdata("this is callbackdata");
   }

}

现在,在您要被调用的类中,实现上面的Created Interface。并且还传递要回调的类的“ this”对象/引用。

public class Main implements dataFetchDelegate
{       
    public static void main( String[] args )
    {
        new Main().getDatafromBackend();
    }

    public void getDatafromBackend() {
        BackendManager inc = new BackendManager();
        //Pass this object as reference.in this Scenario this is Main Object            
        inc.Delegate = this;
        //make call
        inc.getData();
    }

    //This method is called after task/Code Completion
    public void didFetchdata(String callbackData) {
        // TODO Auto-generated method stub
        System.out.println(callbackData);
    }
}
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.