Javaのコールバック関数


177

Javaメソッドでコールバック関数を渡す方法はありますか?

私が模倣しようとしている動作は、関数に渡される.Net Delegateです。

別のオブジェクトを作成することを提案している人を見たことがありますが、それはやり過ぎのようですが、やり過ぎが唯一の方法であることは知っています。


51
Javaは機能しないので、やり過ぎです(それは冗談になるはずです...)。
キース・ピンソン、2011

回答:


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

23
@オマール、同意した。私はC#での長い経験の後にJavaに戻ってきましたが、ラムダ/デリゲートが本当に恋しいです。Javaに来て!
Drew Noakes、

4
@DrewNoakes、朗報です、Java 8にはラムダ(ほとんど)があります...
Lucas

2
単一のメソッドでインターフェイスVisitorを定義したので、代わりに一致するラムダを渡すことができます。
Joey Baruch

43

Java 8以降、ラムダとメソッドの参照があります。

たとえば、次のよう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
コールバックの場合、各コールバックタイプには通常複数の構文があるため、独自の機能インターフェイスを作成することをお勧めします。
スリハルシャチラカパティ2014

よくわかりません。のクラスの1つがjava.util.functionあなたが探しているものであるなら、あなたは行ってもいいです。次に、I / Oのジェネリックを操作できます。(?)
Juh_ 2014

あなたは私を理解していませんでした、私が言ったのは、コールバック関数を使用するC ++コードをJava 8に変換することについてです。生産コード。
スリハルシャチラカパティ2014

2
私の結論は、ほとんどの場合、デフォルトで提供されているものでjava.util.functionは十分ではないため、独自の機能インターフェースを作成する必要があるということです。
スリハルシャチラカパティ14

1
既存の機能インターフェイスと新しいインターフェイスの作成方法に関するメモを追加しました
Juh_

28

簡単にするために、Runnableを使用できます。

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

使用法:

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

2
単純なコールバックを行うためだけに新しいインターフェースやクラスを作成する必要がないため、これが好きです。先端をありがとう!
Bruce

1
public interface SimpleCallback { void callback(Object... objects); }これは非常に簡単で、いくつかのパラメーターを渡す必要がある場合にも役立ちます。
マルコC.

@cprcrackにrun()関数に値を渡す方法はありませんか?
Chris Chevalier

16

少しつまらない:

別のオブジェクトを作成することを提案している人がいるようですが、やり過ぎです

コールバックを渡すことには、ほぼすべてのOO言語で個別のオブジェクトを作成することが含まれるため、過剰と見なすことはほとんどできません。おそらくあなたが意味することは、Javaでは別のクラスを作成する必要があるということです。これは、明示的なファーストクラスの関数またはクロージャーを使用する言語よりも冗長(そしてリソース集約的)です。ただし、匿名クラスは少なくとも冗長性を減らし、インラインで使用できます。


12
はい、それは私が意味したことです。30ほどのイベントでは、最終的に30のクラスになります。
Omar Kooheji

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

これは、ラムダを使用する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

1
うん。適切なラムダ構文が維持されている限り、任意の数の引数(またはなし)が機能します。
gk bwg rhb mbreafteahbtehnb 2017年

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

ちょっとやりすぎ(/ me ducks)
Diederik

使用の数とプロジェクトのサイズに依存します。少数のクラスのプロジェクトではやり過ぎ、大きなプロジェクトでは実用的です。
Juh_ 2014年

また、@ monnooの回答もご覧ください
Juh_

5

メソッドは(まだ)Javaのファーストクラスのオブジェクトではありません。関数ポインタをコールバックとして渡すことはできません。代わりに、必要なメソッドを含むオブジェクト(通常はインターフェースを実装)を作成し、それを渡します。

Javaでのクロージャーの提案(あなたが探している振る舞いを提供するでしょう)が出されましたが、次のJava 7リリースには含まれません。


1
「メソッドは(まだ)Javaのファーストクラスのオブジェクトではありません」-まあ、確かにインスタンスを渡すことができるメソッドclass [1]があります。これは、Javaに期待されるような、クリーンで慣用的なOOコードではありませんが、便利な場合があります。確かに考慮すべき何か。[1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
ジョナスKölker

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を引数として渡しますか?
TWiStErRob 2013

@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 2013

@TWiStErRob、良い点!argTypes配列を手動で渡すことができる関数を追加したので、nullNullPointerExceptionが発生することなく引数/パラメーターを渡すことができます。出力例:NullParameterTest: String parameter=null, int parameter=888
LiuYan刘研

3

CallbackDelegateパターンを使用して行うこともできます。

Callback.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

少し古いですが、それでも... int / Integerのようなプリミティブ型では機能しないという点を除いて、Peter Wilkinsonの答えは素晴らしいと思いました。問題はの.getClass()forでありparameters[i]、たとえばを返しますがjava.lang.Integer、これはによって正しく解釈されません。getMethod(methodName,parameters[])(Javaのエラー)では ...

私はそれをダニエル・スピーワクの提案と組み合わせました(これに対する彼の答え。成功へのステップには、キャッチ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 2015年

はい、スーパークラスからのコールバックにポリモーフィズムを使用しています。ポリモーフィズムだけではこの機能は得られません。SomethingクラスにあるusingCallback()を呼び出しています。次に、ユーザーが関数test()を記述したサブクラスを呼び出します。
avatar1337 2015年

2
コールバックは、重要な何かが発生したときにobject別のユーザーに「通知」するように設計されているobjectため、2番目objectはイベントを処理できます。あなたabstract classは決して別々objectではありません、それはただ別々classです。1つだけを使用して、さまざまな機能を実行するために異なるものobjectを取得classesしています。これは、ポリモーフィズムの本質であり、コールバックパターンではありません。
simo.3792 2015年

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);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.