web-dev-qa-db-fra.com

Fonctions de rappel dans Java

Est-il possible de passer une fonction de rappel dans une méthode Java?

Le comportement que j'essaie d'imiter est un délégué .Net transmis à une fonction.

J'ai vu des personnes suggérer de créer un objet séparé, mais cela semble excessif, mais je suis conscient que parfois, il est parfois excessif de faire de même.

161
Omar Kooheji

Si vous parlez de quelque chose comme un délégué anonyme .NET, je pense que la classe anonyme de Java peut également être utilisée.

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

    }
}
149
Gant

Depuis Java 8, il existe des références à lambda et à une méthode:

Par exemple, définissons:

public class FirstClass {
    String prefix;
    public FirstClass(String prefix){
        this.prefix = prefix;
    }
    public String addPrefix(String suffix){
        return prefix +":"+suffix;
    }
}

et

import Java.util.function.Function;

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

Ensuite, vous pouvez faire:

FirstClass first = new FirstClass("first");
SecondClass second = new SecondClass();
System.out.println(second.applyFunction("second",first::addPrefix));

Vous pouvez trouver un exemple sur github, ici: julien-diener/MethodReference .

31
Juh_

Pour plus de simplicité, vous pouvez utiliser un Runnable:

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

Usage:

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

Un peu nitpicking:

J'ai l'impression que des gens suggèrent de créer un objet séparé, mais cela semble excessif

Passer un rappel inclut la création d'un objet séparé dans à peu près n'importe quel langage OO, de sorte qu'il peut difficilement être considéré comme excessif. Ce que vous voulez probablement dire, c'est qu'en Java, il est nécessaire de créer une classe séparée, plus détaillée (et plus gourmande en ressources) que dans les langages dotés de fonctions ou de fermetures explicites de première classe. Cependant, les classes anonymes réduisent au moins la verbosité et peuvent être utilisées en ligne.

16
Michael Borgwardt

pourtant, je vois qu’il existe un moyen privilégié qui correspond à ce que je cherchais. Il est essentiellement dérivé de ces réponses, mais j’ai dû le manipuler pour le rendre plus redondant et efficace. et je pense que tout le monde cherche ce que je viens avec

Jusqu'au point::

d'abord faire une interface aussi simple que cela

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

maintenant, pour que ce rappel s'exécute chaque fois que vous souhaitez traiter les résultats - plus probablement après un appel asynchrone et si vous voulez exécuter des tâches qui dépendent de ces réponses

// 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 est la fonction qui prend un certain temps pour lui ajouter un rappel afin de vous avertir de l'arrivée des résultats. Ajoutez l'interface de rappel en tant que paramètre de cette méthode.

j'espère que mon point est clair, profitez-en;)

13
Biskrem Muhammad

Ceci est très facile dans Java 8 avec 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();
    }
}

L’idée de mettre en œuvre l’utilisation de la bibliothèque de réflexions a été intéressante et j’ai proposé ce qui, à mon avis, fonctionne assez bien. Le seul inconvénient est de perdre le temps de compilation en vérifiant que vous transmettez des paramètres valides.

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

Vous l'utilisez comme ça

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

Une méthode n'est pas (encore) un objet de première classe en Java; vous ne pouvez pas transmettre un pointeur de fonction en tant que rappel. Au lieu de cela, créez un objet (qui implémente généralement une interface) contenant la méthode dont vous avez besoin et transmettez-le.

Des propositions de fermetures en Java - qui fourniraient le comportement que vous recherchez - ont été faites, mais aucune ne sera incluse dans la prochaine version de Java 7.

5
erickson

Lorsque j'ai besoin de ce type de fonctionnalité en Java, j'utilise généralement le modèle Observer . Cela implique un objet supplémentaire, mais je pense que c'est une façon propre de procéder et qu'il s'agit d'un motif largement compris, qui aide à la lisibilité du code.

4
MattK

Vérifiez les fermetures de la manière dont elles ont été mises en œuvre dans la bibliothèque lambdaj. Ils ont en fait un comportement très similaire à celui des délégués C #:

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

4
Mario Fusco

Vous pouvez aussi faire leCallback en utilisant le motif Delegate:

Rappel.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);
    }
}
3
Sébastien

J'ai essayé d'utiliser Java.lang.reflect pour implémenter 'callback', voici un exemple:

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 ();
    }
}
3
LiuYan 刘研

c'est un peu vieux, mais néanmoins ... J'ai trouvé la réponse de Peter Wilkinson Nice sauf que cela ne fonctionne pas pour les types primitifs comme int/Integer. Le problème est la .getClass() pour le parameters[i], qui retourne par exemple Java.lang.Integer, qui ne sera par contre pas interprétée correctement par getMethod(methodName,parameters[]) (faute de Java) ...

Je l'ai combiné avec la suggestion de Daniel Spiewak ( dans sa réponse à ceci ); Les étapes à suivre sont les suivantes: intercepter NoSuchMethodException -> getMethods() -> rechercher celui qui correspond par method.getName() -> puis faire une boucle explicite dans la liste des paramètres et appliquer la solution de Daniels, en identifiant les correspondances de type et les correspondances de signature.

1
monnoo

J'ai récemment commencé à faire quelque chose comme ça:

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
joey baruch
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"

En gros, si vous voulez faire l'objet d'une interface, ce n'est pas possible, car l'interface ne peut pas avoir d'objets.

L'option consiste à laisser une classe implémenter l'interface, puis appeler cette fonction à l'aide de l'objet de cette classe. Mais cette approche est vraiment verbeuse.

Sinon, écrivez new HelloWorld () (* oberserve c'est une interface et non une classe), puis suivez-le avec la définition des méthodes d'interface elles-mêmes. (* Cette définition est en réalité la classe anonyme). Ensuite, vous obtenez la référence d'objet à travers laquelle vous pouvez appeler la méthode elle-même.

0

Créez une interface et créez la même propriété d'interface dans la classe de rappel.

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

}

Maintenant, dans la classe où vous voulez être rappelé, implémentez l'interface créée ci-dessus. et passez également "cet" objet/référence de votre classe à rappeler.

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);
    }
}
0
Balu mallisetty

Je pense que l'utilisation d'une classe abstraite est plus élégante, comme ceci:

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