【Java】コールバック関数を実装する ~自作インターフェースで使って、実装編~

コールバック関数とは

 * あるクラスに、コールバックしてもらう関数を教えて、その関数が呼び出されるのを待つこと。

Java での実装方法

 * 3つの方法があるかと。

 [1] 自作インターフェースで使って、実装する
 [2] CountDownLatch を使って、実装する
 [3] Future インターフェースを使って、実装する

今回は、「[1] 自作インターフェースで使って、実装する」を取り上げる(他は下記【関連記事】を参照のこと)

実装ポイント

 [1] インターフェースを用意する(ICallback)
 [2] そのインターフェースを実装したクラスのオブジェクトごと、処理実行先(Task)に渡す
 [3] 処理実行先で処理が完了したら、コールバック関数を実行してもらう
  ⇒コールバック関数を実現

サンプル1

ICallback.java

public interface ICallback {
   // Point[1]!
   public void callback();
}

TaskHandler.java

import java.util.Calendar;

public class TaskHandler implements ICallback {
   private ITask task;

   public TaskHandler(ITask task) {
      this.task = task;
   }

   public void start() {
      // Point[2]!!
      this.task.setCallback(this);
      this.task.execute();
   }

   // Callback
   @Override
   public void callback() {
      System.out.println("callback " + Calendar.getInstance().getTime().toString());
   }
}

ITask.java

public interface ITask {
   public void setCallback(ICallback callback);
   public void execute();
}

Task.java

import java.util.Calendar;

public class Task implements ITask {
   private ICallback callback;

   public void setCallback(ICallback callback) {
      this.callback = callback;
   }

   public void execute() {
      // 処理実行
      System.out.println("execute() " + Calendar.getInstance().getTime().toString());
      try {
         Thread.sleep(3000);
      } catch (InterruptedException ignore) {
      }
      
      // 処理実行終了通知
      System.out.println("execute() done " + Calendar.getInstance().getTime().toString());
      this.callback.callback();
   }
}

Main.java

import java.util.Calendar;

public class Main {
   public static void main(String[] args) {
      System.out.println("Start " + Calendar.getInstance().getTime().toString());
      
      TaskHandler handler = new TaskHandler(new Task());
      System.out.println("Call start() " + Calendar.getInstance().getTime().toString());
      handler.start();
      
      System.out.println("Done " + Calendar.getInstance().getTime().toString());
   }
}

出力結果

Start Fri Nov 14 23:48:56 JST 2014
Call start() Fri Nov 14 23:48:56 JST 2014
execute() Fri Nov 14 23:48:56 JST 2014
execute() done Fri Nov 14 23:48:59 JST 2014
callback Fri Nov 14 23:48:59 JST 2014
Done Fri Nov 14 23:48:59 JST 2014

サンプル2

 * 処理が完了したことを通知するコールバック。
   ただし、処理が重かった時を考慮して、Timeout(10000L=10秒)できるようにしておく。

 => カウントダウンラッチ(CountDownLatch) で実装可能。
http://blogs.yahoo.co.jp/dk521123/33538428.html

Main.java

import java.util.Calendar;

public class Main {
   public static void main(String[] args) {
      System.out.println(Calendar.getInstance().getTime().toString()
            + " START");
      SampleTaskManager sampleService = new SampleTaskManager();
      sampleService.start();
      System.out.println(Calendar.getInstance().getTime().toString()
            + " END");
   }
}

Main.java

public interface ICallback {
   void done();
}

SampleTaskManager.java

import java.util.Calendar;
import java.util.concurrent.atomic.AtomicBoolean;

public class SampleTaskManager implements ICallback {
   private AtomicBoolean isDone;
   
   public SampleTaskManager() {
      this.isDone = new AtomicBoolean(false);
   }
   
   @Override
   public void done() {
      this.isDone.set(true);
   }
   
   public void start() {
      System.out.println(Calendar.getInstance().getTime().toString()
            + " start() START");
      Thread sampleTask = new Thread(new SampleTask(this));
      long now = System.currentTimeMillis();
      long timeout = now + 10000L;
      sampleTask.start();
      while (System.currentTimeMillis() < timeout) {
         try {
            if (this.isDone.get()) {
               System.out.println(Calendar.getInstance().getTime().toString()
                     + " Done");
               break;
            }
            Thread.sleep(50L);
         } catch (InterruptedException e) {
         }
      }
      System.out.println(Calendar.getInstance().getTime().toString()
            + " Normal Done? : " + this.isDone.get());
      
      System.out.println(Calendar.getInstance().getTime().toString()
            + " start() END");
   }
}

SampleTask.java

import java.util.Calendar;

public class SampleTask implements Runnable {
   private ICallback callback;
   
   SampleTask(ICallback callback) {
      this.callback = callback;
   }
   
   @Override
   public void run() {
      System.out.println(Calendar.getInstance().getTime().toString()
            + " SampleTask START");
      long sleepTime = (long) (1000L * (Math.random() * 9 + 1));
      // long sleepTime = (long) (10000L * (Math.random() * 9 + 1)); // ★ここ★
      try {
         System.out.println("SampleTask.sleepTime = " + sleepTime);
         System.out.println(Calendar.getInstance().getTime().toString()
               + " SampleTask.Sleeping ... ");
         Thread.sleep(sleepTime);
         this.callback.done();
      } catch (Exception ex) {
         ex.printStackTrace();
      }
      
      System.out.println(Calendar.getInstance().getTime().toString()
            + " SampleTask END");
   }
}

出力結果1

Thu Nov 20 23:49:46 JST 2014 START
Thu Nov 20 23:49:46 JST 2014 start() START
Thu Nov 20 23:49:46 JST 2014 SampleTask START
SampleTask.sleepTime = 9311
Thu Nov 20 23:49:46 JST 2014 SampleTask.Sleeping ... 
Thu Nov 20 23:49:55 JST 2014 SampleTask END
Thu Nov 20 23:49:55 JST 2014 Done
Thu Nov 20 23:49:55 JST 2014 Normal Done? : true
Thu Nov 20 23:49:55 JST 2014 start() END
Thu Nov 20 23:49:55 JST 2014 END

出力結果2

SampleTask.java の「★ここ★」の行のコメントアウトし、上の行をコメントした場合
Thu Nov 20 23:55:46 JST 2014 START
Thu Nov 20 23:55:46 JST 2014 start() START
Thu Nov 20 23:55:46 JST 2014 SampleTask START
SampleTask.sleepTime = 34161
Thu Nov 20 23:55:46 JST 2014 SampleTask.Sleeping ... 
Thu Nov 20 23:55:56 JST 2014 Normal Done? : false // 処理(Task)が完了できず。
Thu Nov 20 23:55:56 JST 2014 start() END
Thu Nov 20 23:55:56 JST 2014 END
Thu Nov 20 23:56:20 JST 2014 SampleTask END



関連記事

コールバック関数を実装する ~Future インターフェースを使って、実装編~

http://blogs.yahoo.co.jp/dk521123/34313495.html

CountDownLatch ~カウントダウンラッチ~

http://blogs.yahoo.co.jp/dk521123/33538428.html