编程成语言:在Java中定义回调的最优雅方法是什么?

时间:2009-05-01 20:03:51

标签: java callback

在“代码完整”一书中,作者讨论了将编程为一种语言(而不是用语言编程)。他的意思是,你不应该通过所选编程语言的限制来限制自己。

回调是一种经常使用的功能。我很感兴趣:将回调编程到java语言中最优雅的方法是什么?

6 个答案:

答案 0 :(得分:6)

Java会针对各种情况使用各种回调。自从AWT听众最古老的时代以来,Java一直都是关于回调的。

Java回调有两种基本的“风格”。第一个是实现接口方法:

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

Java回调的第二种风格是匿名内部类:

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

还有其他方法,包括使用Reflection,使用单独的事件处理类和适配器模式。

答案 1 :(得分:4)

我看到在Java中使用函数指针/委托的最常见方法是使用函子。

基本上,使用单个方法定义接口并使用它的实例作为回调:

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

它比C / C ++或C#等价物更冗长,但它有效。标准库中此模式的一个示例是Comparator接口。

答案 2 :(得分:2)

不幸的是,在Java中,函数不是第一类对象。您可以做的最好是使用界面:

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

答案 3 :(得分:1)

查看这篇文章:

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

回调本质上是一个特殊情况,代表(因为C#有它们),文章给出了一个类似于C#委托的东西的实现,在java中。

答案 4 :(得分:0)

一个非常常见的回调构造是Swing中的事件处理程序,其中ActionListeners可能是最容易掌握的。

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

您经常提供实现适当接口的匿名类的实例,类似于

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

然后将侦听器传递给适当的Swing方法。

答案 5 :(得分:0)

我个人觉得Java迫切需要某种形式的关闭支持。在此期间,我在Java中实现了基于反射的通用方法回调。这是posted on my website

这种方法的好处是它的通用性。目的是能够像文件系统树行走一样编写API,而不必每次都定义一个接口,而是使用API​​在代码中指定一个方法来执行工作。

例如,走一个文件系统树来处理每个文件:

Process Directory树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;
    }

使用Process Directory API

通过编写上述方法,我现在可以非常轻松地处理任何操作的目录树;扫描,计数,列表等。在文件/树删除之类的降序操作之前和之后都可以通过微小的更改来调用目录上的回调(需要一个额外的参数来指示调用的前/后性质) )。

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