Fonctions de rappel en Java


177

Existe-t-il un moyen de passer une fonction de rappel dans une méthode Java?

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

J'ai vu des gens suggérer de créer un objet séparé, mais cela semble exagéré, mais je suis conscient que parfois exagérer est la seule façon de faire les choses.


51
C'est exagéré parce que Java n'est pas fonctionnel (c'est censé être un jeu de mots ...).
Keith Pinson

Réponses:


155

Si vous voulez dire quelque chose comme le 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));

    }
}

3
C'est la méthode canonique depuis Java 1.0.
Charlie Martin le

1
J'ai utilisé ça, c'est slioghtly plus verbeux que ce que je voudrais, mais ça marche.
Omar Kooheji

23
@Omar, d'accord. Je suis revenu à Java après un long passage avec C # et je manque vraiment de lambdas / délégués. Allez Java!
Drew Noakes

4
@DrewNoakes, la bonne nouvelle est que Java 8 a des lambdas (principalement) ...
Lucas

2
puisque vous avez défini l'interface Visiteur avec une seule méthode, vous pouvez passer des lambdas qui correspondent à la place.
Joey Baruch

43

Depuis Java 8, il existe des références lambda et méthodes:

Par exemple, si vous souhaitez une interface fonctionnelle A -> Btelle que:

import java.util.function.Function;

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

alors tu peux l'appeler comme ça

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

Vous pouvez également passer la méthode de classe. Dites que vous avez:

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

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

Ensuite, vous pouvez faire:

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

Remarque :

Ici, j'ai utilisé l'interface fonctionnelle Function<A,B>, mais il y en a beaucoup d'autres dans le package java.util.function. Les plus notables sont

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

et beaucoup d'autres qui se spécialisent sur certains des types d'entrée / sortie. Ensuite, s'il ne fournit pas celui dont vous avez besoin, vous pouvez créer votre propre interface fonctionnelle comme ceci:

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

Exemple d'utilisation:

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

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

1
Pour les CallBacks, il serait préférable d'écrire votre propre interface fonctionnelle car chaque type de rappel aurait généralement plusieurs syntaxes.
Sri Harsha Chilakapati

Je ne suis pas sûr de comprendre. Si l'une des classes de java.util.functionest ce que vous recherchez, alors vous êtes prêt à partir. Ensuite, vous pouvez jouer avec des génériques pour les E / S. (?)
Juh_

Vous ne m'avez pas compris, ce que j'ai dit concerne la traduction de code C ++ qui utilise des fonctions de rappel vers Java 8. Là, pour chaque pointeur de fonction unique, vous devez créer une interface fonctionnelle en Java, car il y aura plus de paramètres dans le réel code de production.
Sri Harsha Chilakapati du

2
Ma conclusion est que la plupart du temps, vous devez écrire vos propres interfaces fonctionnelles car celles fournies par défaut java.util.functionne sont pas suffisantes.
Sri Harsha Chilakapati

1
J'ai ajouté une note sur les interfaces fonctionnelles existantes et comment en créer de nouvelles
Juh_

28

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

2
J'aime ça parce que je n'ai pas besoin de créer une nouvelle interface ou une nouvelle classe juste pour faire un simple rappel. Merci pour le conseil!
Bruce le

1
public interface SimpleCallback { void callback(Object... objects); }Ceci est assez simple et pourrait être utile, vous devez également passer certains paramètres.
Marco C.

@cprcrack pas moyen de passer une valeur dans la fonction run ()?
Chris Chevalier

16

Un peu de pinaillerie:

Il semble que les gens suggèrent de créer un objet séparé mais cela semble exagéré

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


12
Oui, c'est ce que je voulais dire. Avec une trentaine d'événements, vous vous retrouvez avec 30 classes.
Omar Kooheji

16

pourtant je vois qu'il y a la manière la plus préférée qui était ce que je cherchais .. il est fondamentalement dérivé de ces réponses mais j'ai dû le manipuler de manière plus redondante et efficace .. et je pense que tout le monde cherche ce que je propose

Jusqu'au point::

faites d'abord une interface aussi simple

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

maintenant pour exécuter ce rappel quand vous le souhaitez pour gérer les résultats - plus probablement après un appel asynchrone et vous voulez exécuter des choses qui dépendent de ces réutilisations

// 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 vous voulez y ajouter un rappel pour vous avertir lorsque les résultats sont arrivés, ajoutez l'interface de rappel en tant que paramètre de cette méthode

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


11

C'est très facile en Java 8 avec des 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();
    }
}

Sera-ce comme ça en cas de dispute? pastebin.com/KFFtXPNA
Nashev

1
Oui. N'importe quelle quantité d'arguments (ou aucun) fonctionnera tant que la syntaxe lambda appropriée est maintenue.
gk bwg rhb mbreafteahbtehnb

7

J'ai trouvé l'idée d'implémenter en utilisant la bibliothèque Reflect intéressante et j'ai proposé cela qui, je pense, fonctionne assez bien. Le seul inconvénient est de perdre la vérification à la compilation que vous passez 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);
        }
    }
}

On dirait un peu exagéré (/ me canards)
Diederik

dépend du nombre d'utilisation et de la taille du projet: exagéré pour un projet de quelques classes, pratique sur un grand.
Juh_

Jetez également un œil à la réponse de @monnoo
Juh_

5

Une méthode n'est pas (encore) un objet de première classe en Java; vous ne pouvez pas passer un pointeur de fonction comme 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-la.

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.


1
"Une méthode n'est pas (encore) un objet de première classe en Java" - eh bien, il y a la classe de méthode [1], dont vous pouvez certainement transmettre des instances. Ce n'est pas le code OO propre et idiomatique que vous attendez de java, mais cela pourrait être opportun. Certainement quelque chose à considérer, cependant. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

4

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 manière propre de procéder, et c'est un modèle largement compris, ce qui contribue à la lisibilité du code.



3

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

Comment passez-vous null comme argument?
TWiStErRob

@TWiStErRob, dans cet exemple, ce serait comme timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. la sortie seranull test: [null]
LiuYan 刘 研

Ne serait-ce pas NPE args[i].getClass()? Mon point est que cela ne fonctionne pas si vous sélectionnez une méthode basée sur des types d'arguments. Cela fonctionne avec String.format, mais peut ne pas fonctionner avec quelque chose d'autre qui accepte null.
TWiStErRob

@TWiStErRob, bon point! J'ai ajouté une fonction qui peut passer manuellement un argTypestableau, donc maintenant nous pouvons passer un nullargument / paramètre sans que NullPointerException ne se soit produit. Exemple de sortie:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研

3

Vous pouvez également faire le en Callbackutilisant le Delegatemodèle:

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

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

c'est un peu vieux, mais quand même ... J'ai trouvé la réponse de Peter Wilkinson sympa à part le fait qu'elle ne fonctionne pas pour les types primitifs comme int / Integer. Le problème est le .getClass()pour le parameters[i], qui retourne par exemple java.lang.Integer, qui par contre ne sera pas correctement interprété par getMethod(methodName,parameters[])(faute de Java) ...

Je l'ai combiné avec la suggestion de Daniel Spiewak ( dans sa réponse à cela ); les étapes du succès comprenaient: attraper NoSuchMethodException-> getMethods()-> rechercher celui correspondant par method.getName()-> puis parcourir explicitement la liste des paramètres et appliquer la solution Daniels, en identifiant les correspondances de type et les correspondances de signature.


0

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

C'est du polymorphisme, pas un rappel. Vous devez vérifier le modèle de rappel.
simo.3792

Oui, j'utilise le polymorphisme pour le rappel de la super classe. Vous n'obtenez pas cette fonctionnalité simplement par polymorphisme. J'appelle usingCallback () qui est dans la classe Something, puis il rappelle la sous-classe où l'utilisateur a écrit sa fonction test ().
avatar1337

2
Les rappels sont conçus pour que l'un object"avertisse" un autre objectlorsque quelque chose d'important s'est produit, afin que le second objectpuisse gérer l'événement. Votre abstract classn'est jamais un séparé object, ce n'est qu'un séparé class. Vous n'en utilisez qu'un objectet vous devenez différent classespour exécuter différentes fonctions, ce qui est l'essence même du polymorphisme, pas le modèle de rappel.
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"

Fondamentalement, si vous voulez créer l'objet d'une interface, ce n'est pas possible, car l'interface ne peut pas avoir d'objets.

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

Alternativement, écrivez new HelloWorld () (* oberserve c'est une interface pas une classe) puis suivez-le avec la définition des méthodes d'interface elle-même. (* 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 souhaitez ê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);
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.