Programming into a language: What is the most elegant way to define callbacks in Java?

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

  •  03-07-2019
  •  | 
  •  

Question

In the book 'Code Complete' the author talks about programming into a language (instead of programming in a language). He means, that you should not limit yourself by the restrictions of the chosen programming-language.

A callback is an often used feature. I'm interested: What is the most elegant way to programm callbacks into the java-language?

Was it helpful?

Solution

Check out this article:

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

Callbacks are essentially a special case od Delegates (As C# has them), and the article gives an implementation of something resembling a C# delegate, in java.

OTHER TIPS

Java uses all sorts of callbacks for all sorts of situations. Ever since the oldest old days of AWT listeners, Java has been all about callbacks.

There are two basic "flavors" of Java callbacks. The first is the implement an interface method:

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

The second flavor of Java callback is the anonymous inner class:

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

There are other ways, too, including using Reflection, using separate event-handling classes, and the Adapter pattern.

The most common way I've seen to work around the absense of function pointers/delegates in Java is to use functors.

Basically, define an interface with a single method and use instances of it as your callback:

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

Its alot more verbose than the C/C++ or C# equivalents, but it works. An example of this pattern in the standard library is the Comparator interface.

Unfortunately, in Java, functions are not first-class objects. The best you can do is to use an 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
            }
        });
    }
}

A very common call back construction is the event handlers in Swing, where ActionListeners are probably the most simple to grasp.

Have a look at http://java.sun.com/docs/books/tutorial/uiswing/events/actionlistener.html

You very often provide an instance of an anonymous class implementing the appropriate interface, similar to

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

where you then pass listener to an appropriate Swing method.

I personally feel Java desperately needs some form of closure support. In the interim, I have implemented a reflection-based general purpose method callback in Java. It's posted on my website.

The benefit of this approach is it's genericity. The aim was to be able to write API's like file-system tree walking without having to define an interface each time, instead designating a method in the code using the API to perform the work.

For example, walking a file system tree to process each file:

The Process Directory Tree API

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

Using the Process Directory API

With the above method written, I can now very easily process a directory tree for any operation; scanning, counting, listing, etc. With a minor change to invoke the callback on a directory both before and after descending operations like file/tree deletion could also be done (an additional parameter is needed to indicate the pre/post nature of the 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);
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top