Funzioni di callback in Java


177

Esiste un modo per passare una funzione di richiamata in un metodo Java?

Il comportamento che sto cercando di imitare è un delegato .Net che viene passato a una funzione.

Ho visto persone suggerire di creare un oggetto separato, ma questo sembra eccessivo, tuttavia sono consapevole che a volte l'eccesso è l'unico modo per fare le cose.


51
È eccessivo perché Java non è funzionale (dovrebbe essere un gioco di parole ...).
Keith Pinson,

Un altro esempio di java 8: stackoverflow.com/questions/14319787/…
Vadzim,

Risposte:


155

Se intendi qualcosa come un delegato anonimo di .NET, penso che possa essere usata anche la classe anonima di 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
Questo è il metodo canonico da Java 1.0.
Charlie Martin,

1
Sono stato usign questo, è decisamente più prolisso di quello che mi piacerebbe, ma funziona.
Omar Kooheji,

23
@Omar, d'accordo. Sono tornato a Java dopo un lungo periodo in C # e mi mancano davvero lambda / delegati. Dai Java!
Drew Noakes,

4
@DrewNoakes, la buona notizia è che Java 8 ha lambdas (principalmente) ...
Lucas

2
poiché hai definito l'interfaccia Visitor con un solo metodo, puoi passare lambdas che corrispondono invece di esso.
Joey Baruch,

43

Da Java 8, ci sono riferimenti lambda e metodo:

Ad esempio, se si desidera un'interfaccia funzionale A -> Bcome:

import java.util.function.Function;

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

allora puoi chiamarlo così

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

Inoltre puoi passare il metodo di classe. Dì che hai:

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

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

Quindi puoi fare:

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

Nota :

Qui ho usato l'interfaccia funzionale Function<A,B>, ma ce ne sono molti altri nel pacchetto java.util.function. I più importanti sono

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

e molti altri specializzati in alcuni tipi di input / output. Quindi, se non fornisce quello di cui hai bisogno, puoi creare la tua interfaccia funzionale in questo modo:

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

Esempio di utilizzo:

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

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

1
Per i CallBack, sarebbe meglio scrivere la propria interfaccia funzionale poiché ogni tipo di callback di solito avrebbe più sintassi.
Sri Harsha Chilakapati,

Non sono sicuro di capire. Se una delle lezioni java.util.functionè quella che stai cercando, allora sei a posto. Quindi puoi giocare con i generici per l'I / O. (?)
Giovedì

Non mi hai capito, quello che ho detto riguarda la traduzione del codice C ++ che utilizza le funzioni di callback in Java 8, lì, per ogni puntatore di funzione univoco, devi creare un'interfaccia funzionale in Java, poiché ci saranno più parametri nella realtà codice di produzione.
Sri Harsha Chilakapati,

2
La mia conclusione è che la maggior parte delle volte, è necessario scrivere le proprie interfacce funzionali poiché quelle predefinite fornite java.util.functionnon sono sufficienti.
Sri Harsha Chilakapati,

1
Ho aggiunto una nota sulle interfacce funzionali esistenti e su come crearne di nuove
1919

28

Per semplicità, puoi usare un Runnable :

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

Uso:

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

2
Mi piace perché non ho bisogno di creare una nuova interfaccia o classe solo per fare un semplice callback. Grazie per il consiglio!
Bruce,

1
public interface SimpleCallback { void callback(Object... objects); }Questo è abbastanza semplice e potrebbe essere utile anche passare alcuni parametri.
Marco C.,

@cprcrack non c'è modo di passare un valore nella funzione run ()?
Chris Chevalier,

16

Un piccolo pignolo:

Sembra che le persone suggeriscano di creare un oggetto separato, ma sembra eccessivo

Passare un callback include la creazione di un oggetto separato in quasi tutti i linguaggi OO, quindi difficilmente può essere considerato eccessivo. Ciò che probabilmente intendi è che in Java, ti richiede di creare una classe separata, che è più dettagliata (e più intensiva in termini di risorse) rispetto ai linguaggi con funzioni o chiusure esplicite di prima classe. Tuttavia, le classi anonime riducono almeno la verbosità e possono essere utilizzate in linea.


12
Sì, questo è ciò che intendevo. Con circa 30 eventi finisci con 30 lezioni.
Omar Kooheji,

16

eppure vedo che c'è il modo più preferito che era quello che stavo cercando .. è fondamentalmente derivato da queste risposte ma ho dovuto manipolarlo per renderlo più ridondante ed efficiente .. e penso che tutti stiano cercando ciò che mi viene in mente

Al punto::

prima fai un'interfaccia così semplice

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

ora per eseguire questo callback ogni volta che si desidera fare per gestire i risultati - più probabilmente dopo la chiamata asincrona e si desidera eseguire alcune cose che dipendono da questi risultati

// 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 è la funzione che richiede un po 'di tempo per aggiungere un callback ad esso per avvisare quando arrivano i risultati, aggiungere l'interfaccia di callback come parametro a questo metodo

spero che il mio punto sia chiaro, divertiti;)


11

Questo è molto semplice in Java 8 con lambdas.

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

Sarà così in caso di discussione? pastebin.com/KFFtXPNA
Nashev,

1
Sì. Qualsiasi quantità di argomenti (o nessuno) funzionerà fino a quando viene mantenuta la sintassi lambda corretta.
gk bwg rhb mbreafteahbtehnb

7

Ho trovato interessante l'idea di implementare usando la biblioteca di reflusso e ho trovato questo che penso funzioni abbastanza bene. L'unico lato negativo è perdere il controllo del tempo di compilazione che si sta passando parametri validi.

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

Lo usi così

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

Sembra un po 'eccessivo (/ io anatre)
Diederik,

dipende dal numero di utilizzo e dalle dimensioni del progetto: eccessivo per un progetto di poche classi, pratico su uno di grandi dimensioni.
Giovedì

Inoltre, dai un'occhiata alla risposta di @monnoo
Juh_

5

Un metodo non è (ancora) un oggetto di prima classe in Java; non è possibile passare un puntatore a funzione come callback. Invece, crea un oggetto (che di solito implementa un'interfaccia) che contenga il metodo che ti serve e passalo.

Sono state fatte proposte di chiusure in Java, che fornirebbero il comportamento che stai cercando, ma nessuna sarà inclusa nella prossima versione di Java 7.


1
"Un metodo non è (ancora) un oggetto di prima classe in Java" - beh, c'è la classe method [1], di cui puoi certamente passare delle istanze. Non è il codice OO pulito, idiomatico che ti aspetteresti da Java, ma potrebbe essere opportuno. Certamente qualcosa da considerare, però. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker,

4

Quando ho bisogno di questo tipo di funzionalità in Java, di solito uso il modello Observer . Implica un oggetto in più, ma penso che sia un modo pulito di procedere, ed è un modello ampiamente compreso, che aiuta con la leggibilità del codice.



3

Ho provato a usare java.lang.reflect per implementare 'callback', ecco un esempio:

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

Come si passa null come arg?
TWiStErRob,

@TWiStErRob, in questo esempio, sarebbe come timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. l'uscita sarànull test: [null]
LiuYan 刘 研

Non sarebbe NPE acceso args[i].getClass()? Il mio punto è che non funziona se si seleziona il metodo in base ai tipi di argomento. Funziona con String.format, ma potrebbe non funzionare con qualcos'altro che accetta null.
TWiStErRob,

@TWiStErRob, buon punto! Ho aggiunto una funzione che può passare manualmente argTypesarray, quindi ora possiamo passare nullargomento / parametro senza che si sia verificato NullPointerException. Esempio di output:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研

3

Puoi anche Callbackusare il Delegatemodello:

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

Di recente ho iniziato a fare qualcosa del genere:

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

è un po 'vecchio, ma comunque ... Ho trovato piacevole la risposta di Peter Wilkinson, tranne per il fatto che non funziona per tipi primitivi come int / Integer. Il problema è il .getClass()per parameters[i], che restituisce per esempio java.lang.Integer, che d'altra parte non sarà interpretato correttamente da getMethod(methodName,parameters[])(errore di Java) ...

L'ho combinato con il suggerimento di Daniel Spiewak ( nella sua risposta a questo ); i passaggi per il successo includevano: prendere NoSuchMethodException-> getMethods()-> cercare quello corrispondente per method.getName()-> e quindi scorrere esplicitamente l'elenco dei parametri e applicare la soluzione Daniels, ad esempio identificando le corrispondenze di tipo e le corrispondenze della firma.


0

Penso che usare una classe astratta sia più elegante, in questo modo:

// 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
*/

Questo è polimorfismo, non un richiamo. È necessario controllare il modello di richiamata.
simo.3792,

Sì, sto usando il polimorfismo per richiamare dalla super classe. Questa funzionalità non si ottiene semplicemente dal polimorfismo. Sto chiamando usingCallback () che è nella classe Something, quindi richiama la sottoclasse in cui l'utente ha scritto il suo test di funzione ().
avatar 1337

2
I callback sono progettati per object"notificare" un altro objectquando si è verificato qualcosa di significativo, in modo che il secondo objectpossa gestire l'evento. Il tuo abstract classnon è mai un separato object, è solo un separato class. Ne stai usando solo uno objecte diventi diverso classesper svolgere funzioni diverse, che è l'essenza del polimorfismo, non il modello di callback.
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"

Fondamentalmente se si desidera creare l'oggetto di un'interfaccia non è possibile, poiché l'interfaccia non può avere oggetti.

L'opzione è consentire a qualche classe di implementare l'interfaccia e quindi chiamare quella funzione usando l'oggetto di quella classe. Ma questo approccio è davvero dettagliato.

In alternativa, scrivi il nuovo HelloWorld () (* oberserve questa è un'interfaccia non una classe) e poi seguila con la definizione dei metodi dell'interfaccia stessa. (* Questa definizione è in realtà la classe anonima). Quindi si ottiene il riferimento all'oggetto attraverso il quale è possibile chiamare il metodo stesso.


0

Creare un'interfaccia e creare la stessa proprietà dell'interfaccia nella classe di callback.

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

}

Ora nella classe in cui vuoi essere richiamato, implementa l'interfaccia creata sopra. e passa anche "questo" oggetto / riferimento della tua classe da richiamare.

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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.