Funciones de devolución de llamada en Java

177

¿Hay alguna manera de pasar una función de devolución de llamada en un método Java?

El comportamiento que intento imitar es un delegado .Net que se pasa a una función.

He visto personas sugiriendo crear un objeto separado, pero eso parece excesivo, sin embargo, soy consciente de que a veces el exceso es la única forma de hacer las cosas.

Omar Kooheji
fuente
51
Es excesivo porque Java no es funcional (se supone que es un juego de palabras ...).
Keith Pinson el
Otro ejemplo de Java 8: stackoverflow.com/questions/14319787/…
Vadzim

Respuestas:

155

Si te refieres a algo como delegado anónimo .NET, creo que la clase anónima de Java también se puede usar.

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
fuente
3
Este es el método canónico desde Java 1.0.
Charlie Martin el
1
He estado usando esto, es ligeramente más detallado de lo que me gustaría, pero funciona.
Omar Kooheji
23
@ Omar, de acuerdo. Regresé a Java después de un largo período con C # y realmente extraño lambdas / delegados. Vamos Java!
Drew Noakes
44
@DrewNoakes, la buena noticia es que Java 8 tiene lambdas (en su mayoría) ...
Lucas
2
Como ha definido la interfaz Visitor con un único método, puede pasar lambdas que coincidan en lugar de él.
Joey Baruch
43

Desde Java 8, hay referencias lambda y de métodos:

Por ejemplo, si desea una interfaz funcional A -> Bcomo:

import java.util.function.Function;

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

entonces puedes llamarlo así

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

También puedes pasar el método de clase. Digamos que tienes:

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

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

Entonces puedes hacer:

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

Nota :

Aquí utilicé la interfaz funcional Function<A,B>, pero hay muchos otros en el paquete java.util.function. Los más notables son

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

y muchos otros que se especializan en algunos de los tipos de entrada / salida. Luego, si no proporciona el que necesita, puede crear su propia interfaz funcional de la siguiente manera:

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

Ejemplo de uso:

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

computeAnswer((question, a, b) -> question + "42");
// "6*9=42"
Juh_
fuente
1
Para CallBacks, sería mejor escribir su propia interfaz funcional ya que cada tipo de devolución de llamada generalmente tendría múltiples sintaxis.
Sri Harsha Chilakapati
No estoy seguro de entender. Si una de las clases java.util.functiones lo que estás buscando, entonces estás listo para comenzar. Entonces puedes jugar con genéricos para la E / S. (?)
Juh_
No me entendiste, lo que dije es sobre la traducción de código C ++ que usa funciones de devolución de llamada a Java 8. Allí, para cada puntero de función único, tienes que crear una interfaz funcional en Java, ya que habrá más parámetros en la realidad Codigo de producción.
Sri Harsha Chilakapati
2
Mi conclusión es que la mayoría de las veces, se requiere que escriba sus propias interfaces funcionales, ya que las predeterminadas provistas java.util.functionno son suficientes.
Sri Harsha Chilakapati
1
Agregué
28

Para simplificar, puede usar un Runnable :

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

Uso:

runCallback(new Runnable()
{
    @Override
    public void run()
    {
        // Running callback
    }
});
cprcrack
fuente
2
Me gusta esto porque no necesito crear una nueva interfaz o clase solo para hacer una simple devolución de llamada. ¡Gracias por el consejo!
Bruce
1
public interface SimpleCallback { void callback(Object... objects); }Esto es bastante sencillo y podría ser útil si necesita pasar algunos parámetros también.
Marco C.
@cprcrack ¿no hay forma de pasar un valor en la función run ()?
Chris Chevalier
16

Un pequeño truco:

Parece que la gente sugiere crear un objeto separado, pero eso parece excesivo

Pasar una devolución de llamada incluye crear un objeto separado en prácticamente cualquier lenguaje OO, por lo que difícilmente se puede considerar excesivo. Lo que probablemente quiere decir es que en Java, requiere que cree una clase separada, que es más detallada (y más intensiva en recursos) que en lenguajes con funciones explícitas o cierres de primera clase. Sin embargo, las clases anónimas al menos reducen la verbosidad y se pueden usar en línea.

Michael Borgwardt
fuente
12
Sí, eso es lo que quise decir. Con más o menos 30 eventos terminas con 30 clases.
Omar Kooheji
16

Sin embargo, veo que es la forma más preferida, que era lo que estaba buscando ... básicamente se deriva de estas respuestas, pero tuve que manipularlo para que sea más redundante y eficiente ... y creo que todos buscan lo que se me ocurrió.

Al punto::

primero haga una interfaz así de simple

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

ahora para hacer que esta devolución de llamada se ejecute siempre que desee hacer para manejar los resultados, más probable después de una llamada asincrónica y desea ejecutar algunas cosas que dependen de estos reinicios

// 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 es la función que lleva algún tiempo que desee agregarle una devolución de llamada para notificarle cuándo llegaron los resultados, agregue la interfaz de devolución de llamada como parámetro de este método

espero que mi punto sea claro, disfruta;)

Biskrem Muhammad
fuente
11

Esto es muy fácil en 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();
    }
}
gk bwg rhb mbreafteahbtehnb
fuente
¿Será así en caso de discusión? pastebin.com/KFFtXPNA
Nashev
1
Sí. Cualquier cantidad de argumentos (o ninguno) funcionará mientras se mantenga la sintaxis lambda adecuada.
gk bwg rhb mbreafteahbtehnb
7

La idea de implementar usando la biblioteca reflect me pareció interesante y se me ocurrió esto, que creo que funciona bastante bien. El único inconveniente es perder el tiempo de compilación de que está pasando parámetros válidos.

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 usas así

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
fuente
Parece un poco exagerado (/ yo patos)
Diederik
depende del número de uso y el tamaño del proyecto: exageración para un proyecto de pocas clases, práctico en uno grande.
Juh_
Además, eche un vistazo a la respuesta de @monnoo
Juh_
5

Un método no es (todavía) un objeto de primera clase en Java; no puede pasar un puntero de función como devolución de llamada. En su lugar, cree un objeto (que generalmente implementa una interfaz) que contiene el método que necesita y páselo.

Se han hecho propuestas para cierres en Java, que proporcionarían el comportamiento que está buscando, pero ninguna se incluirá en la próxima versión de Java 7.

erickson
fuente
1
"Un método no es (todavía) un objeto de primera clase en Java" - bueno, está la clase de método [1], de la cual ciertamente puede pasar instancias. No es el código limpio, idiomático, OO que esperarías de Java, pero podría ser conveniente. Ciertamente, algo a considerar, sin embargo. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker
4

Cuando necesito este tipo de funcionalidad en Java, generalmente uso el patrón Observer . Sí implica un objeto extra, pero creo que es un camino limpio, y es un patrón ampliamente entendido, que ayuda con la legibilidad del código.

MattK
fuente
3

Intenté usar java.lang.reflect para implementar 'devolución de llamada', aquí hay una muestra:

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 刘 研
fuente
¿Cómo pasas nulo como un argumento?
TWiStErRob
@TWiStErRob, en esta muestra, sería como timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. la salida seránull test: [null]
LiuYan 刘 研
¿No sería NPE args[i].getClass()? Mi punto es que no funciona si selecciona un método basado en tipos de argumento. Funciona con String.format, pero puede no funcionar con otra cosa que acepte null.
TWiStErRob
@TWiStErRob, ¡Buen punto! He agregado una función que puede pasar manualmente la argTypesmatriz, por lo que ahora podemos pasar nullargumentos / parámetros sin que ocurra NullPointerException. Salida de muestra:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研
3

También puedes hacer el Callbackuso del Delegatepatrón:

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
fuente
1

Recientemente comencé a hacer algo como esto:

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
fuente
1

es un poco viejo, pero sin embargo ... la respuesta de Peter Wilkinson me pareció agradable, excepto por el hecho de que no funciona para tipos primitivos como int / Integer. El problema es .getClass()para el parameters[i], que devuelve por ejemplo java.lang.Integer, que por otro lado no será interpretado correctamente porgetMethod(methodName,parameters[]) (culpa de Java) ...

Lo combiné con la sugerencia de Daniel Spiewak ( en su respuesta a esto ); los pasos para el éxito incluyeron: capturar NoSuchMethodException-> getMethods()-> buscar la coincidencia por method.getName()-> y luego recorrer explícitamente la lista de parámetros y aplicar la solución Daniels, como identificar las coincidencias de tipo y las coincidencias de firma.

monnoo
fuente
0

Creo que usar una clase abstracta es más elegante, así:

// 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
fuente
Esto es polimorfismo, no una devolución de llamada. Debe verificar el patrón de devolución de llamada.
simo.3792
Sí, estoy usando polimorfismo para devolver la llamada de la superclase. No obtienes esta funcionalidad simplemente por polimorfismo. Estoy llamando usando Callback () que está en la clase Something, luego vuelve a llamar a la subclase donde el usuario ha escrito su función test ().
avatar1337
2
Las devoluciones de llamada están diseñadas para que uno object"notifique" a otro objectcuando ha ocurrido algo significativo, para que el segundo objectpueda manejar el evento. Tu abstract classnunca es un separado object, es solo un separado class. Solo está utilizando uno objecty se está volviendo diferente classespara realizar diferentes funciones, que es la esencia del polimorfismo, no el patrón de devolución de llamada.
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"

Básicamente, si desea hacer que el objeto de una interfaz no sea posible, porque la interfaz no puede tener objetos.

La opción es dejar que alguna clase implemente la interfaz y luego llamar a esa función usando el objeto de esa clase. Pero este enfoque es realmente detallado.

Alternativamente, escriba un nuevo HelloWorld () (* observe que esta es una interfaz, no una clase) y luego siga con la definición de los métodos de interfaz en sí. (* Esta definición es en realidad la clase anónima). Luego obtienes la referencia del objeto a través del cual puedes llamar al método en sí.

Abhishek Abhyankar
fuente
0

Cree una interfaz y cree la misma propiedad de interfaz en la clase de devolución de llamada.

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

}

Ahora, en la clase donde desea que le devuelvan la llamada, implemente la interfaz creada anteriormente. y también pase "este" objeto / referencia de su clase para que se le devuelva la llamada.

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
fuente