Callback в чистом виде, это когда вы сформировали задачу и отправили её выполнять какому то там процессу, но не ждёте её завершения и продолжаете работать, а чтобы к вам мог вернутся результат работы, вы для воркера передаёте ссылку на свой класс с кастом в интерфейс калбека в котором обьявлен метод, при помощи которого воркер может вернуть данные. Вся работа происходит асинхронно без блокировок и ожиданий. В этом смысл калбеков, так как некоторые задачи могут идти долго, а некоторые быстро, но каллер не должен об этом заботиться, он просто предоставляет интерфейс для ответа и продолжает работу.
import java.util.ArrayList; import java.util.Random; /** * * @author mutagen */ public class CallBackDemo { public static void main(String[] args) { Caller caller = new Caller(); for (int i = 0; i < 10; i++) { new Worker(caller).start(); } // итого у нас 9 воркеров отработали и вернули ответ по калбеку в коллекцию System.out.println(caller.getStatuses()); } static class Caller implements Callback { private ArrayList<Integer> statuses = new ArrayList<>(); public ArrayList<Integer> getStatuses() { return statuses; } @Override public void callMeBack(int status) { // тут может быть логика куда вернутся данные, может быть коллекция, я выбрал попроще synchronized (statuses) { statuses.add(status); } } } static interface Callback { // подготовим интерфейс по которому нам будут возвращать данные void callMeBack(int status); } static class Worker extends Thread { private Callback cb; public Worker(Callback cb) { this.cb = cb; } int pleaseDoMeAFavor() { return new Random().nextInt(); } @Override public void run() { // выполним работу int st = pleaseDoMeAFavor(); // и вернём данные вызывающему по калбек интерфейсу cb.callMeBack(st); } } }
Допустим, мы разрабатываем фреймворк. У нас есть определенный шаблон действий, в котором только один пункт мы не можем прописать жестко и в этом месте необходимо, чтобы пользователь вставил свой функционал. Это можно сделать абстрактным методом, но в этом случае проблема: во-первых мы не сможем динамически менять такие «вставки», во-вторых, для 1000 функционалов мы получим огромную иерархию наследования. Кроме того, так как множественное наследование в Java не поддерживается, наследование можно использовать только в отдельных случаях. В этом случае можно использовать коллбэки.
Яркий пример использования колбэков — Spring-овские темплейты для работы с JDBC — они исполняют общие функции самостоятельно, передавая пользователю только такие операции, как обработка ResultSet-ов.
У нас есть темплейтный класс:
package callbacks; import java.util.Arrays; import java.util.List; public class Template { private ICustomOperation callback; public Template(ICustomOperation callback) { this.callback = callback; } public void setCallback(ICustomOperation callback) { this.callback = callback; } public void processData(List<String> lines) { for (String line : lines) { System.out.println("Input: " + line); line = "[" + line + "]"; System.out.println("Standart operation 1 result : " + line); line = line.replaceAll(" ", "_"); System.out.println("Standart operation 2 result : " + line); line = callback.evaluate(line); // здесь мы будем передавать контроль над обработкой пользователю System.out.println("Custom operation result : " + line); line = line.toLowerCase(); System.out.println("Standart operation 3 result : " + line); } } }
Есть колбэк-интерфейс:
package callbacks; public interface ICustomOperation { public String evaluate(String input); }
И различные его реализации:
package callbacks; public class CustomOperation implements ICustomOperation { public String evaluate(String input) { return "CUSTOM: " + input; } }
package callbacks; public class AnotherCustomOperation implements ICustomOperation { public String evaluate(String input) { return "ANOTHER CUSTOM: " + input; } }
Теперь запуск:
Template template = new Template(new CustomOperation()); List<String> data = Arrays.asList(new String[] { "first line", "SEcOnD LiNe" }); template.processData(data); template.setCallback(new AnotherCustomOperation()); template.processData(data);
Есть ещё одно применение: допустим, у нас есть объект, у которого private метод, который вы не хотите делать public или protected. Вы вызваете метод другого класса и изнутри него хотите сделать вызов нашего private-метода.Это можно сделать следующим образом:
package callbacks;
package callbacks; public class PrivateFieldHolder { private OuterCaller outerCaller; public OuterCaller getOuterCaller() { return outerCaller; } public void setOuterCaller(OuterCaller outerCaller) { this.outerCaller = outerCaller; } public void publicMethod() { System.out.println("Holder: public method call"); // здесь хитрая штука: мы передаем в вызов метода // внешнего класса коллбэк, который // является анонимным классом внутри данного класса // т.к. этот анонимный класс видит private-метод, то мы // можем поместить внутрь callPrivateMethod вызов этого // private-метода. Таким образом при вызове // callPrivateMethod во внешнем классе позволит обратиться // к private-методу этого класса. outerCaller.callFromOutsidePrivateMethod(new IPrivateMethodCallback() { public void callPrivateMethod(String caller) { privateMethod(caller); } }); } private void privateMethod(String caller) { System.out.println(caller + ": private method call"); } }
Колбэк-интерфейс:
package callbacks; public interface IPrivateMethodCallback { public void callPrivateMethod(String caller); }
Внешний класс, которому нужно вызвать private-метод:
package callbacks; public class OuterCaller { public void callFromOutsidePrivateMethod(IPrivateMethodCallback callback) { System.out.println("Outer method call"); callback.callPrivateMethod("Outer caller"); } }
Теперь запуск:
PrivateFieldHolder pfh = new PrivateFieldHolder(); OuterCaller oc = new OuterCaller(); pfh.setOuterCaller(oc); pfh.publicMethod();