Rückruffunktionen in Java

176

Gibt es eine Möglichkeit, eine Rückruffunktion in einer Java-Methode zu übergeben?

Das Verhalten, das ich nachahmen möchte, ist ein .NET-Delegat, der an eine Funktion übergeben wird.

Ich habe Leute gesehen, die vorgeschlagen haben, ein separates Objekt zu erstellen, aber das scheint übertrieben, aber ich bin mir bewusst, dass manchmal Overkill der einzige Weg ist, Dinge zu tun.

Omar Kooheji
quelle
51
Es ist übertrieben, weil Java nicht funktioniert (das soll ein Wortspiel sein ...).
Keith Pinson
Ein weiteres Java 8 Beispiel: stackoverflow.com/questions/14319787/…
Vadzim

Antworten:

155

Wenn Sie so etwas wie einen anonymen .NET-Delegaten meinen, kann auch die anonyme Klasse von Java verwendet werden.

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

    }
}
Gant
quelle
3
Dies ist die kanonische Methode seit Java 1.0.
Charlie Martin
1
Ich habe dies verwendet, es ist etwas ausführlicher als das, was ich möchte, aber es funktioniert.
Omar Kooheji
23
@ Omar, stimmte zu. Ich bin nach einer langen Zeit mit C # zu Java zurückgekehrt und vermisse Lambdas / Delegierte wirklich. Komm schon Java!
Drew Noakes
4
@DrewNoakes, gute Nachricht ist, Java 8 hat Lambdas (meistens) ...
Lucas
2
Da Sie die Schnittstelle Visitor mit einer einzigen Methode definiert haben, können Sie stattdessen Lambdas übergeben, die übereinstimmen.
Joey Baruch
42

Seit Java 8 gibt es Lambda und Methodenreferenzen:

Wenn Sie beispielsweise eine funktionale Schnittstelle wünschen, A -> Bwie z.

import java.util.function.Function;

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

dann kann man es so nennen

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

Sie können auch die Klassenmethode übergeben. Angenommen, Sie haben:

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

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

Dann können Sie tun:

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

Hinweis :

Hier habe ich die Funktionsschnittstelle verwendet Function<A,B>, aber es gibt viele andere im Paket java.util.function. Die bemerkenswertesten sind

  • Supplier:: void -> A
  • Consumer:: A -> void
  • BiConsumer:: (A,B) -> void
  • Function:: A -> B
  • BiFunction:: (A,B) -> C

und viele andere, die sich auf einige der Eingabe- / Ausgabetypen spezialisiert haben. Wenn es dann nicht das bietet, was Sie benötigen, können Sie Ihre eigene Funktionsschnittstelle wie folgt erstellen:

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

Anwendungsbeispiel:

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

computeAnswer((question, a, b) -> question + "42");
// "6*9=42"
Juh_
quelle
1
Für CallBacks ist es besser, eine eigene Funktionsschnittstelle zu schreiben, da jeder Rückruftyp normalerweise mehrere Syntaxen hat.
Sri Harsha Chilakapati
Ich bin mir nicht sicher zu verstehen. Wenn eine der Klassen das java.util.functionist, wonach Sie suchen, dann können Sie loslegen. Dann können Sie mit Generika für die E / A spielen. (?)
Juh_
Sie haben mich nicht verstanden. Was ich gesagt habe, ist die Übersetzung von C ++ - Code, der Rückruffunktionen verwendet, nach Java 8. Dort müssen Sie für jeden eindeutigen Funktionszeiger eine Funktionsschnittstelle in Java erstellen, da es in der Realität mehr Parameter gibt produktionscode.
Sri Harsha Chilakapati
2
Mein Fazit ist, dass Sie die meiste Zeit Ihre eigenen Funktionsschnittstellen schreiben müssen, da die standardmäßig bereitgestellten java.util.functionnicht ausreichen.
Sri Harsha Chilakapati
1
Ich habe einen Hinweis zu vorhandenen Funktionsschnittstellen und zum Erstellen neuer hinzugefügt
Juh_
28

Der Einfachheit halber können Sie ein Runnable verwenden :

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

Verwendung:

runCallback(new Runnable()
{
    @Override
    public void run()
    {
        // Running callback
    }
});
cprcrack
quelle
2
Ich mag das, weil ich keine neue Schnittstelle oder Klasse erstellen muss, nur um einen einfachen Rückruf durchzuführen. Danke für den Tipp!
Bruce
1
public interface SimpleCallback { void callback(Object... objects); }Dies ist recht einfach und kann nützlich sein, wenn Sie auch einige Parameter übergeben müssen.
Marco C.
@cprcrack keine Möglichkeit, einen Wert in der Funktion run () zu übergeben?
Chris Chevalier
16

Ein bisschen Trottel:

Ich habe Leute, die vorschlagen, ein separates Objekt zu erstellen, aber das scheint übertrieben

Das Übergeben eines Rückrufs umfasst das Erstellen eines separaten Objekts in nahezu jeder OO-Sprache, sodass es kaum als Overkill angesehen werden kann. Was Sie wahrscheinlich meinen, ist, dass Sie in Java eine separate Klasse erstellen müssen, die ausführlicher (und ressourcenintensiver) ist als in Sprachen mit expliziten erstklassigen Funktionen oder Abschlüssen. Anonyme Klassen reduzieren jedoch zumindest die Ausführlichkeit und können inline verwendet werden.

Michael Borgwardt
quelle
12
Ja das habe ich gemeint. Bei ungefähr 30 Veranstaltungen erhalten Sie 30 Klassen.
Omar Kooheji
16

Dennoch sehe ich, dass es den am meisten bevorzugten Weg gibt, nach dem ich gesucht habe. Er leitet sich im Wesentlichen aus diesen Antworten ab, aber ich musste ihn redundanter und effizienter manipulieren. Und ich denke, jeder sucht nach dem, was mir einfällt

Auf den Punkt ::

Machen Sie zuerst eine Schnittstelle so einfach

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

Jetzt können Sie diesen Rückruf ausführen, wann immer Sie möchten, um die Ergebnisse zu verarbeiten - wahrscheinlicher nach einem asynchronen Aufruf, und Sie möchten einige Dinge ausführen, die von diesen Wiederverwendungen abhängen

// 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 ist die Funktion, die einige Zeit in Anspruch nimmt, wenn Sie einen Rückruf hinzufügen möchten, um Sie über die Ergebnisse zu informieren. Fügen Sie der Methode die Rückrufschnittstelle als Parameter hinzu

hoffe mein Punkt ist klar, viel Spaß;)

Biskrem Muhammad
quelle
11

Dies ist in Java 8 mit Lambdas sehr einfach.

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();
    }
}
gk bwg rhb mbreafteahbtehnb
quelle
Wird es bei Argumenten so sein? pastebin.com/KFFtXPNA
Nashev
1
Ja. Eine beliebige Anzahl von Argumenten (oder keine) funktioniert, solange die richtige Lambda-Syntax beibehalten wird.
gk bwg rhb mbreafteahbtehnb
7

Ich fand die Idee, die Reflect-Bibliothek zu implementieren, interessant und kam auf diese Idee, die meiner Meinung nach recht gut funktioniert. Der einzige Nachteil ist, dass Sie die Überprüfung der Kompilierungszeit verlieren, wenn Sie gültige Parameter übergeben.

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

Du benutzt es so

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);
        }
    }
}
Peter Wilkinson
quelle
Sieht ein bisschen aus wie Overkill (/ me ducks)
Diederik
hängt von der Anzahl der Anwendungen und der Größe des Projekts ab: Overkill für ein Projekt mit wenigen Klassen, praktisch für ein großes Projekt.
Juh_
Schauen Sie sich auch die Antwort von @monnoo
Juh_
5

Eine Methode ist (noch) kein erstklassiges Objekt in Java; Sie können einen Funktionszeiger nicht als Rückruf übergeben. Erstellen Sie stattdessen ein Objekt (das normalerweise eine Schnittstelle implementiert), das die von Ihnen benötigte Methode enthält, und übergeben Sie diese.

Es wurden Vorschläge für Schließungen in Java gemacht, die das von Ihnen gesuchte Verhalten liefern würden. In der kommenden Java 7-Version werden jedoch keine Vorschläge enthalten sein.

erickson
quelle
1
"Eine Methode ist (noch) kein erstklassiges Objekt in Java" - nun, es gibt die Methodenklasse [1], von der Sie sicherlich Instanzen weitergeben können. Es ist nicht der saubere, idiomatische OO-Code, den Sie von Java erwarten würden, aber es könnte zweckmäßig sein. Auf jeden Fall etwas zu beachten. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker
4

Wenn ich diese Art von Funktionalität in Java benötige, verwende ich normalerweise das Observer-Muster . Es impliziert zwar ein zusätzliches Objekt, aber ich denke, es ist ein sauberer Weg und ein weithin verstandenes Muster, das die Lesbarkeit des Codes verbessert.

MattK
quelle
4

Überprüfen Sie die Abschlüsse, wie sie in der Lambdaj-Bibliothek implementiert wurden. Sie haben tatsächlich ein Verhalten, das C # -Delegierten sehr ähnlich ist:

http://code.google.com/p/lambdaj/wiki/Closures

Mario Fusco
quelle
3

Ich habe versucht, mit java.lang.reflect 'Rückruf' zu implementieren. Hier ein Beispiel:

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 ();
    }
}
LiuYan 刘 研
quelle
Wie übergibt man null als Argument?
TWiStErRob
@ TWiStErRob, in diesem Beispiel wäre es wie timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. Ausgabe wird seinnull test: [null]
LiuYan
Wäre es nicht NPE args[i].getClass()? Mein Punkt ist, dass es nicht funktioniert, wenn Sie eine Methode basierend auf Argumenttypen auswählen. Es funktioniert mit String.format, aber möglicherweise nicht mit etwas anderem, das akzeptiert null.
TWiStErRob
@ TWiStErRob, guter Punkt! Ich habe eine Funktion hinzugefügt, die das argTypesArray manuell übergeben kann , sodass wir jetzt nullArgumente / Parameter übergeben können, ohne dass eine NullPointerException aufgetreten ist. Beispielausgabe:NullParameterTest: String parameter=null, int parameter=888
LiuYan
3

Sie können dies auch Callbackanhand des DelegateMusters tun :

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);
    }
}
Sébastien
quelle
1

Ich habe vor kurzem angefangen, so etwas zu tun:

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);
    }
}
Joey Baruch
quelle
1

es ist ein bisschen alt, aber trotzdem ... Ich fand die Antwort von Peter Wilkinson nett, bis auf die Tatsache, dass es für primitive Typen wie int / Integer nicht funktioniert. Das Problem ist das .getClass()für dieparameters[i] , das zum Beispiel zurückgibt java.lang.Integer, was andererseits von getMethod(methodName,parameters[])(Javas Fehler) nicht richtig interpretiert wird ...

Ich habe es mit dem Vorschlag von Daniel Spiewak kombiniert ( in seiner Antwort darauf ); Zu den Schritten zum Erfolg gehörten: Abfangen NoSuchMethodException-> getMethods()-> Suchen nach dem passenden durch method.getName()-> und anschließendes explizites Durchlaufen der Liste der Parameter und Anwenden der Daniels-Lösung, z. B. Identifizieren der Typübereinstimmungen und der Signaturübereinstimmungen.

monnoo
quelle
0

Ich finde die Verwendung einer abstrakten Klasse eleganter:

// 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
*/
avatar1337
quelle
Dies ist Polymorphismus, kein Rückruf. Sie müssen das Rückrufmuster überprüfen.
simo.3792
Ja, ich benutze Polymorphismus, um von der Superklasse zurückzurufen. Sie erhalten diese Funktionalität nicht einfach durch Polymorphismus. Ich rufe usingCallback () auf, das sich in der Klasse Something befindet, und rufe dann die Unterklasse auf, in die der Benutzer seinen Funktionstest () geschrieben hat.
Avatar1337
2
Rückrufe sind so konzipiert, dass einer objectden anderen "benachrichtigt", objectwenn etwas Wesentliches aufgetreten ist, sodass der zweite objectdas Ereignis behandeln kann. Du abstract classbist niemals ein Separater object, es ist nur ein Separater class. Sie verwenden nur eine objectund werden unterschiedlich classes, um unterschiedliche Funktionen auszuführen. Dies ist die Essenz des Polymorphismus und nicht das Rückrufmuster.
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"

Grundsätzlich ist es nicht möglich, das Objekt einer Schnittstelle zu erstellen, da die Schnittstelle keine Objekte haben kann.

Die Option besteht darin, eine Klasse die Schnittstelle implementieren zu lassen und diese Funktion dann mit dem Objekt dieser Klasse aufzurufen. Aber dieser Ansatz ist wirklich ausführlich.

Alternativ können Sie eine neue HelloWorld () schreiben (* oberserve dies ist eine Schnittstelle, keine Klasse) und anschließend die Definition der Schnittstellenmethoden selbst vornehmen. (* Diese Definition ist in Wirklichkeit die anonyme Klasse). Dann erhalten Sie die Objektreferenz, über die Sie die Methode selbst aufrufen können.

Abhishek Abhyankar
quelle
0

Erstellen Sie eine Schnittstelle und erstellen Sie dieselbe Schnittstelleneigenschaft in der Rückrufklasse.

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

}

Implementieren Sie nun in der Klasse, in der Sie zurückgerufen werden möchten, die oben erstellte Schnittstelle. und übergeben Sie auch "dieses" Objekt / Referenz Ihrer Klasse, um zurückgerufen zu werden.

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);
    }
}
Balu Mallisetty
quelle