Programmation dans un langage: Quel est le moyen le plus élégant de définir des rappels en Java?

StackOverflow https://stackoverflow.com/questions/813013

  •  03-07-2019
  •  | 
  •  

Question

Dans le livre "Code Complete", l'auteur parle de la programmation dans dans une langue (au lieu d'une programmation dans une langue). Il veut dire que vous ne devriez pas vous limiter aux restrictions du langage de programmation choisi.

Un rappel est une fonctionnalité souvent utilisée. Je suis intéressé: Quel est le moyen le plus élégant de programmer des rappels en langage java?

Était-ce utile?

La solution

Découvrez cet article:

http://www.onjava.com /pub/a/onjava/2003/05/21/delegates.html

Les rappels sont essentiellement un cas particulier des délégués (comme C # les a), et l'article donne une implémentation de quelque chose qui ressemble à un délégué C #, en java.

Autres conseils

Java utilise toutes sortes de callbacks pour toutes sortes de situations. Depuis le plus ancien âge des auditeurs AWT, Java est l’objet de rappels.

Il existe deux "saveurs" de base. des callbacks Java. La première est la méthode implémenter une interface:

public class MyThing implements StateChangeListener {

   //this method is declared in StateChangeListener
   public void stateChanged() {
      System.out.println("Callback called!");
   }

   public MyThing() {
      //Here we declare ourselves as a listener, which will eventually
      //lead to the stateChanged method being called.
      SomeLibraryICareAbout.addListener(this);
   }
}

La deuxième variante du rappel Java est la classe interne anonyme:

public class MyThing {

   public MyThing() {
      //Here we declare ourselves as a listener, which will eventually
      //lead to the stateChanged method being called.
      SomeLibraryICareAbout.addListener( new StateChangeListener() {
          //this method is declared in StateChangeListener
          public void stateChanged() {
              System.out.println("Callback called!");
          }
      });
   }
}

Il existe également d'autres méthodes, notamment utiliser Reflection, utiliser des classes de gestion d'événements distinctes et le modèle Adapter.

La manière la plus courante que j’ai vu de travailler sur l’absence de pointeurs / délégués de fonctions en Java est d’utiliser des foncteurs.

En gros, définissez une interface avec une seule méthode et utilisez-en des instances comme rappel:

public interface Callback<T,V>{
  public T invoke(V context);
}

C'est beaucoup plus bavard que les équivalents C / C ++ ou C #, mais cela fonctionne. L’interface Comparator est un exemple de ce modèle dans la bibliothèque standard.

Malheureusement, en Java, les fonctions ne sont pas des objets de première classe. Le mieux que vous puissiez faire est d'utiliser une interface:

public interface MyCallback
{
    public void theCallback(int arg);
}

public class Sample
{
    public static void takesACallback(MyCallback callback)
    {
        ...
        callback.theCallback(arg);
    }
}

public class Sample2
{
    public static void main(String[] args)
    {
        Sample.takesACallback(new MyCallback()
        {
            void theCallback(int arg)
            {
                // do a little dance
            }
        });
    }
}

Les gestionnaires d’événements de Swing constituent une structure de rappel très courante, les ActionListeners étant probablement les plus simples à comprendre.

Consultez http: //java.sun .com / docs / books / tutorial / uiswing / events / actionlistener.html

Vous fournissez très souvent une instance d'une classe anonyme implémentant l'interface appropriée, similaire à

listener = new ActionListener() {
  public void actionPerformed(ActionEvent e) {
     // do stuff...
  }
};

où vous passez ensuite listener à une méthode Swing appropriée.

Personnellement, j'estime que Java a désespérément besoin d'une forme de support de fermeture. Dans l'intervalle, j'ai implémenté une méthode de rappel basée sur la réflexion à base de méthode générale en Java. Il est publié sur mon site Web .

L'avantage de cette approche est sa généricité. L’objectif était de pouvoir écrire des API comme une arborescence de système de fichiers sans avoir à définir une interface à chaque fois, mais en désignant une méthode dans le code en utilisant l’API pour effectuer le travail.

Par exemple, parcourir une arborescence de système de fichiers pour traiter chaque fichier:

API d'arborescence de répertoires de processus

/**
 * Process a directory using callbacks.  To interrupt, the callback must throw an (unchecked) exception.
 * Subdirectories are processed only if the selector is null or selects the directories, and are done
 * after the files in any given directory.  When the callback is invoked for a directory, the file
 * argument is null;
 * <p>
 * The callback signature is:
 * <pre>    void callback(File dir, File ent);</pre>
 * <p>
 * @return          The number of files processed.
 */
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
    int                                 cnt=0;

    if(!dir.isDirectory()) {
        if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
        }
    else {
        cbk.invoke(dir,(Object[])null);

        File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
        if(lst!=null) {
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(!ent.isDirectory()) {
                    cbk.invoke(dir,ent);
                    lst[xa]=null;
                    cnt++;
                    }
                }
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
                }
            }
        }
    return cnt;
    }

Utilisation de l'API Process Directory

La méthode ci-dessus étant écrite, je peux maintenant très facilement traiter une arborescence de répertoires pour toute opération. numériser, compter, lister, etc. Une modification mineure permettant d’appeler le rappel sur un répertoire avant et après des opérations descendantes telles que la suppression de fichier / arborescence peut également être effectuée (un paramètre supplémentaire est nécessaire pour indiquer la nature préalable / postérieure de l’invocation. ).

static private final Method             COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);

...

IoUtil.processDirectory(root,new Callback(this,COUNT),selector);

...

private void callback_count(File dir, File fil) {
    if(fil!=null) {                                                             // file is null for processing a directory
        fileTotal++;
        if(fil.length()>fileSizeLimit) {
            throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
            }
        }
    progress("Counting",dir,fileTotal);
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top