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