Я реализовал поддержку обратного вызова / делегата в Java, используя отражение. Подробности и рабочий источник доступны на моем сайте .
Как это работает
Существует основной класс с именем Callback с вложенным классом с именем WithParms. API, которому необходим обратный вызов, примет объект Callback в качестве параметра и, если необходимо, создаст Callback.WithParms в качестве переменной метода. Поскольку многие приложения этого объекта будут рекурсивными, это работает очень чисто.
Поскольку производительность по-прежнему остается для меня высоким приоритетом, я не хотел создавать массив одноразовых объектов для хранения параметров для каждого вызова - в конце концов, в большой структуре данных могут быть тысячи элементов, и в сценарий обработки сообщений, мы можем обработать тысячи структур данных в секунду.
Чтобы быть потокобезопасным, массив параметров должен существовать уникально для каждого вызова метода API, и для эффективности тот же самый должен использоваться для каждого вызова обратного вызова; Мне нужен был второй объект, который было бы дешево создать, чтобы связать обратный вызов с массивом параметров для вызова. Но в некоторых случаях вызывающий уже имел массив параметров по другим причинам. По этим двум причинам массив параметров не принадлежит объекту Callback. Кроме того, выбор вызова (передача параметров в виде массива или отдельных объектов) принадлежит API, использующему обратный вызов, позволяющий ему использовать любой вызов, наиболее подходящий для его внутренней работы.
Тогда вложенный класс WithParms является необязательным и служит двум целям: он содержит массив объектов параметров, необходимый для вызовов обратного вызова, и предоставляет 10 перегруженных методов invoke () (с 1 до 10 параметрами), которые загружают параметр массив, а затем вызвать цель обратного вызова.
Ниже приведен пример использования обратного вызова для обработки файлов в дереве каталогов. Это начальный этап проверки, который просто подсчитывает файлы для обработки и гарантирует, что ни один из них не превысит заранее определенный максимальный размер. В этом случае мы просто создаем обратный вызов, встроенный в вызов 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);
}
IoUtil.processDirectory ():
<code>/**
* 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);
*
* @return Количество обработанных файлов.
* /
static public int processDirectory (File dir, Callback cbk, FileSelector sel) {
return _processDirectory (dir, новый 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 ++; }
}
еще {
cbk.invoke (реж, (Object []) NULL);
File [] lst = (sel == null? Dir.listFiles (): dir.listFiles (sel));
if (lst! = null) {
для (int xa = 0; xa
Этот пример иллюстрирует всю прелесть этого подхода - специфичная логика приложения абстрагируется в обратный вызов, и трудоемкость рекурсивного обхода дерева каталогов полностью убрана с помощью полностью повторно используемого статического служебного метода. И нам не нужно многократно платить за определение и реализацию интерфейса для каждого нового использования. Конечно, аргумент для интерфейса заключается в том, что он гораздо более четко описывает, что нужно реализовать (это принудительно, а не просто задокументировано) - но на практике я не нашел, что это проблема для получения обратного вызова определение верно.
Определение и реализация интерфейса на самом деле не так уж плохи (если только вы не распространяете апплеты, как я, где на самом деле важно избегать создания дополнительных классов), но это действительно хорошо, когда у вас есть несколько обратных вызовов в одном классе. Мало того, что они вынуждены помещать каждого из них в отдельный внутренний класс, добавляющий накладные расходы в развернутом приложении, это также просто утомительно для программирования, и весь этот код на самом деле просто "шум".