tags:

views:

407

answers:

7

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?

+2  A: 

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
            }
        });
    }
}
Adam Rosenfield
Yeah, I don't think "elegant" and "callback" go together in Java.
Michael Myers
I would say that "elegant" and "Java" don't go together.
Jordan Miner
+2  A: 

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.

Kevin Montrose
Just don't put "I" in front of the interface name.
Steve Kuo
Bleedover from all the win32 I've been digging into lately.
Kevin Montrose
Give your Callback a meaningful name, something like FooCallback, callback by itself is too generic and doesnt help understand its relationship to other components in the system.
mP
More verbose than C++? Are you talking C++0x?
Tom Hawtin - tackline
In c++ you can pass a pointer to a function around. For example I could write a function whose argument type was "int (*)(float, char, char)"e.g void myFunct( int (*pt2Function)(float, char, char) , ostream }The Runnable and Callable interfaces, and teh AWTEventListener hierarchy are also examples of 'functor classes'
KitsuneYMG
+4  A: 

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.

CaptainAwesomePants
+1 for anonymous inner classes
Neil Williams
Anonymous and named listeners are the same thing, a concrete implementation of an interface.
mP
A: 

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.

Thorbjørn Ravn Andersen
+1  A: 

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.

David Reis
That is very much programming into a language. Actually it uses reflection, but with a simple class (Delegator) the reflection-stuff is hidden and the interface presented is much better than the anonymous classes. Great find.
Mnementh
A: 

A related question stemming from this would be Does Java need closures?

eed3si9n
A: 

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