Функции обратного вызова в Java


177

Есть ли способ передать функцию обратного вызова в методе Java?

Поведение, которое я пытаюсь имитировать, - это .Net Delegate, передаваемый в функцию.

Я видел людей, предлагающих создать отдельный объект, но это кажется излишним, однако я знаю, что иногда избыточное убийство - единственный способ сделать что-то.


51
Это излишне, потому что Java нефункциональна (это должно быть каламбуром ...).
Кит Пинсон

Другой пример java 8: stackoverflow.com/questions/14319787/…
Вадим

Ответы:


155

Если вы имеете в виду что-то вроде анонимного делегата .NET, я думаю, что анонимный класс Java также может быть использован.

public class Main {

    public interface Visitor{
        int doJob(int a, int b);
    }


    public static void main(String[] args) {
        Visitor adder = new Visitor(){
            public int doJob(int a, int b) {
                return a + b;
            }
        };

        Visitor multiplier = new Visitor(){
            public int doJob(int a, int b) {
                return a*b;
            }
        };

        System.out.println(adder.doJob(10, 20));
        System.out.println(multiplier.doJob(10, 20));

    }
}

3
Это канонический метод начиная с Java 1.0.
Чарли Мартин

1
Я был знаком с этим, он более многословен, чем мне бы хотелось, но он работает.
Омар Кохеджи

23
@ Омар, согласился. Я вернулся на Java после долгой работы с C # и очень скучаю по лямбда-делегатам. Давай на Яве!
Дрю Ноакс

4
@DrewNoakes, хорошая новость в том, что в Java 8 есть лямбды (в основном) ...
Лукас

2
так как вы определили интерфейс Visitor одним методом, вы можете передавать лямбды, которые соответствуют ему.
Джои Барух,

43

Начиная с Java 8, существуют ссылки на лямбда и методы:

Например, если вы хотите функциональный интерфейс, A -> Bтакой как:

import java.util.function.Function;

public MyClass {
    public static String applyFunction(String name, Function<String,String> function){
        return function.apply(name);
    }
}

тогда вы можете назвать это так

MyClass.applyFunction("42", str -> "the answer is: " + str);
// returns "the answer is: 42"

Также вы можете передать метод класса. Скажем, у вас есть:

@Value // lombok
public class PrefixAppender {
    private String prefix;

    public String addPrefix(String suffix){
        return prefix +":"+suffix;
    }
}

Тогда вы можете сделать:

PrefixAppender prefixAppender= new PrefixAppender("prefix");
MyClass.applyFunction("some text", prefixAppender::addPrefix);
// returns "prefix:some text"

Примечание :

Здесь я использовал функциональный интерфейс Function<A,B>, но в пакете есть много других java.util.function. Самые известные из них

  • Supplier: void -> A
  • Consumer: A -> void
  • BiConsumer: (A,B) -> void
  • Function: A -> B
  • BiFunction: (A,B) -> C

и многие другие, которые специализируются на некоторых типах ввода / вывода. Затем, если он не обеспечивает тот, который вам нужен, вы можете создать свой собственный функциональный интерфейс, например так:

@FunctionalInterface
interface Function3<In1, In2, In3, Out> { // (In1,In2,In3) -> Out
    public Out apply(In1 in1, In2 in2, In3 in3);
}

Пример использования:

String computeAnswer(Function3<String, Integer, Integer, String> f){
    return f.apply("6x9=", 6, 9);
}

computeAnswer((question, a, b) -> question + "42");
// "6*9=42"

1
Для CallBacks было бы лучше написать собственный функциональный интерфейс, поскольку каждый тип обратного вызова обычно имеет несколько синтаксисов.
Шри Харша Чилакапати

Я не уверен, чтобы понять. Если один из классов в java.util.functionтом, что вы ищете, то вы можете пойти. Тогда вы можете играть с дженериками для ввода / вывода. (?)
Juh_

Вы не поняли, что я сказал о переводе кода C ++, который использует функции обратного вызова, в Java 8. Там для каждого уникального указателя функции вы должны создать функциональный интерфейс в Java, так как в реальном времени будет больше параметров производственный код.
Шри Харша Чилакапати

2
Я пришел к выводу, что в большинстве случаев вам необходимо написать свои собственные функциональные интерфейсы, поскольку предоставляемых по умолчанию интерфейсов java.util.functionнедостаточно.
Шри Харша Чилакапати

1
Я добавил заметку о существующих функциональных интерфейсах и о том, как создавать новые
Juh_

28

Для простоты вы можете использовать Runnable :

private void runCallback(Runnable callback)
{
    // Run callback
    callback.run();
}

Использование:

runCallback(new Runnable()
{
    @Override
    public void run()
    {
        // Running callback
    }
});

2
Мне это нравится, потому что мне не нужно создавать новый интерфейс или класс, просто чтобы сделать простой обратный вызов. Спасибо за чаевые!
Брюс

1
public interface SimpleCallback { void callback(Object... objects); }Это довольно просто и может быть полезно, вам нужно также передать некоторые параметры.
Марко С.

@cprcrack нет способа передать значение в функцию run ()?
Крис Шевалье

16

Немного придирки:

Мне кажется, люди предлагают создать отдельный объект, но это кажется излишним

Передача обратного вызова включает создание отдельного объекта практически на любом языке ОО, так что вряд ли это можно считать излишним. Вероятно, вы имеете в виду, что в Java требуется создать отдельный класс, который является более многословным (и более ресурсоемким), чем в языках с явными первоклассными функциями или замыканиями. Тем не менее, анонимные классы, по крайней мере, уменьшают многословие и могут быть использованы встроенными.


12
Да, это то, что я имел в виду. Приблизительно с 30 событиями вы получите 30 классов.
Омар Кохеджи

16

все же я вижу, что есть наиболее предпочтительный способ, который я искал ... он в основном получен из этих ответов, но мне пришлось манипулировать им, чтобы сделать его более излишним и эффективным. и я думаю, что все ищут то, что я придумаю

К точке::

сначала сделать интерфейс, который прост

public interface myCallback {
    void onSuccess();
    void onError(String err);
}

теперь, чтобы запустить этот обратный вызов, когда вам захочется обработать результаты - более вероятно, что после асинхронного вызова вы захотите запустить некоторые вещи, которые зависят от этих результатов.

// import the Interface class here

public class App {

    public static void main(String[] args) {
        // call your method
        doSomething("list your Params", new myCallback(){
            @Override
            public void onSuccess() {
                // no errors
                System.out.println("Done");
            }

            @Override
            public void onError(String err) {
                // error happen
                System.out.println(err);
            }
        });
    }

    private void doSomething(String param, // some params..
                             myCallback callback) {
        // now call onSuccess whenever you want if results are ready
        if(results_success)
            callback.onSuccess();
        else
            callback.onError(someError);
    }

}

doSomething это функция, которая занимает некоторое время, когда вы хотите добавить к ней обратный вызов, чтобы уведомить вас о результатах, добавить интерфейс обратного вызова в качестве параметра к этому методу

надеюсь, моя точка зрения ясна, наслаждайтесь;)


11

Это очень просто в Java 8 с лямбдами.

public interface Callback {
    void callback();
}

public class Main {
    public static void main(String[] args) {
        methodThatExpectsACallback(() -> System.out.println("I am the callback."));
    }
    private static void methodThatExpectsACallback(Callback callback){
        System.out.println("I am the method.");
        callback.callback();
    }
}

Будет ли это так в случае с аргументом? pastebin.com/KFFtXPNA
Нашев

1
Ага. Любое количество аргументов (или ни одного) будет работать, пока поддерживается правильный лямбда-синтаксис.
gk bwg rhb mbreafteahbtehnb

7

Я нашел идею реализации с использованием библиотеки отражений интересной и придумал, что, я думаю, работает довольно хорошо. Единственным недостатком является потеря проверки времени компиляции, что вы передаете действительные параметры.

public class CallBack {
    private String methodName;
    private Object scope;

    public CallBack(Object scope, String methodName) {
        this.methodName = methodName;
        this.scope = scope;
    }

    public Object invoke(Object... parameters) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Method method = scope.getClass().getMethod(methodName, getParameterClasses(parameters));
        return method.invoke(scope, parameters);
    }

    private Class[] getParameterClasses(Object... parameters) {
        Class[] classes = new Class[parameters.length];
        for (int i=0; i < classes.length; i++) {
            classes[i] = parameters[i].getClass();
        }
        return classes;
    }
}

Вы используете это так

public class CallBackTest {
    @Test
    public void testCallBack() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        TestClass testClass = new TestClass();
        CallBack callBack = new CallBack(testClass, "hello");
        callBack.invoke();
        callBack.invoke("Fred");
    }

    public class TestClass {
        public void hello() {
            System.out.println("Hello World");
        }

        public void hello(String name) {
            System.out.println("Hello " + name);
        }
    }
}

Выглядит как перебор (/ me ducks)
Diederik

зависит от количества использования и размера проекта: излишни для малокомпонентного проекта, практично для крупного.
Juh_

Кроме того, взгляните на ответ @monnoo
Juh_

5

Метод не является (пока) первоклассным объектом в Java; Вы не можете передать указатель на функцию в качестве обратного вызова. Вместо этого создайте объект (который обычно реализует интерфейс), который содержит необходимый метод, и передайте его.

Предложения по замыканиям в Java, которые обеспечили бы поведение, которое вы ищете, были сделаны, но ни одно из них не будет включено в предстоящий выпуск Java 7.


1
«Метод не является (пока) первоклассным объектом в Java» - ну, есть класс метода [1], из которого вы, безусловно, можете передавать экземпляры. Это не чистый, идиоматический, ОО-код, который вы ожидаете от Java, но это может быть целесообразно. Конечно, кое-что нужно учитывать. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Джонас Кёлкер

4

Когда мне нужна такая функциональность в Java, я обычно использую шаблон Observer . Это подразумевает дополнительный объект, но я думаю, что это чистый путь, и это широко понятный шаблон, который помогает с читабельностью кода.



3

Я попытался использовать java.lang.reflect для реализации 'обратного вызова', вот пример:

package StackOverflowQ443708_JavaCallBackTest;

import java.lang.reflect.*;
import java.util.concurrent.*;

class MyTimer
{
    ExecutorService EXE =
        //Executors.newCachedThreadPool ();
        Executors.newSingleThreadExecutor ();

    public static void PrintLine ()
    {
        System.out.println ("--------------------------------------------------------------------------------");
    }

    public void SetTimer (final int timeout, final Object obj, final String methodName, final Object... args)
    {
        SetTimer (timeout, obj, false, methodName, args);
    }
    public void SetTimer (final int timeout, final Object obj, final boolean isStatic, final String methodName, final Object... args)
    {
        Class<?>[] argTypes = null;
        if (args != null)
        {
            argTypes = new Class<?> [args.length];
            for (int i=0; i<args.length; i++)
            {
                argTypes[i] = args[i].getClass ();
            }
        }

        SetTimer (timeout, obj, isStatic, methodName, argTypes, args);
    }
    public void SetTimer (final int timeout, final Object obj, final String methodName, final Class<?>[] argTypes, final Object... args)
    {
        SetTimer (timeout, obj, false, methodName, argTypes, args);
    }
    public void SetTimer (final int timeout, final Object obj, final boolean isStatic, final String methodName, final Class<?>[] argTypes, final Object... args)
    {
        EXE.execute (
            new Runnable()
            {
                public void run ()
                {
                    Class<?> c;
                    Method method;
                    try
                    {
                        if (isStatic) c = (Class<?>)obj;
                        else c = obj.getClass ();

                        System.out.println ("Wait for " + timeout + " seconds to invoke " + c.getSimpleName () + "::[" + methodName + "]");
                        TimeUnit.SECONDS.sleep (timeout);
                        System.out.println ();
                        System.out.println ("invoking " + c.getSimpleName () + "::[" + methodName + "]...");
                        PrintLine ();
                        method = c.getDeclaredMethod (methodName, argTypes);
                        method.invoke (obj, args);
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                    finally
                    {
                        PrintLine ();
                    }
                }
            }
        );
    }
    public void ShutdownTimer ()
    {
        EXE.shutdown ();
    }
}

public class CallBackTest
{
    public void onUserTimeout ()
    {
        System.out.println ("onUserTimeout");
    }
    public void onTestEnd ()
    {
        System.out.println ("onTestEnd");
    }
    public void NullParameterTest (String sParam, int iParam)
    {
        System.out.println ("NullParameterTest: String parameter=" + sParam + ", int parameter=" + iParam);
    }
    public static void main (String[] args)
    {
        CallBackTest test = new CallBackTest ();
        MyTimer timer = new MyTimer ();

        timer.SetTimer ((int)(Math.random ()*10), test, "onUserTimeout");
        timer.SetTimer ((int)(Math.random ()*10), test, "onTestEnd");
        timer.SetTimer ((int)(Math.random ()*10), test, "A-Method-Which-Is-Not-Exists");    // java.lang.NoSuchMethodException

        timer.SetTimer ((int)(Math.random ()*10), System.out, "println", "this is an argument of System.out.println() which is called by timer");
        timer.SetTimer ((int)(Math.random ()*10), System.class, true, "currentTimeMillis");
        timer.SetTimer ((int)(Math.random ()*10), System.class, true, "currentTimeMillis", "Should-Not-Pass-Arguments");    // java.lang.NoSuchMethodException

        timer.SetTimer ((int)(Math.random ()*10), String.class, true, "format", "%d %X", 100, 200); // java.lang.NoSuchMethodException
        timer.SetTimer ((int)(Math.random ()*10), String.class, true, "format", "%d %X", new Object[]{100, 200});

        timer.SetTimer ((int)(Math.random ()*10), test, "NullParameterTest", new Class<?>[]{String.class, int.class}, null, 888);

        timer.ShutdownTimer ();
    }
}

Как вы передаете нуль в качестве аргумента?
TWiStErRob

@TWiStErRob, в этом примере это будет похоже timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. выходной будетnull test: [null]
LiuYan 研 研

Разве это не NPE на args[i].getClass()? Я хочу сказать, что это не работает, если вы выбираете метод, основанный на типах аргументов. Это работает с String.format, но может не работать с чем-то еще, что принимает null.
TWiStErRob

@TWiStErRob, Хороший вопрос! Я добавил функцию, которая может передавать argTypesмассив вручную , поэтому теперь мы можем передавать nullаргумент / параметр без возникновения исключения NullPointerException. Пример вывода:NullParameterTest: String parameter=null, int parameter=888
LiuYan 研 研

3

Вы также можете сделать с Callbackпомощью Delegateшаблона:

Callback.java

public interface Callback {
    void onItemSelected(int position);
}

PagerActivity.java

public class PagerActivity implements Callback {

    CustomPagerAdapter mPagerAdapter;

    public PagerActivity() {
        mPagerAdapter = new CustomPagerAdapter(this);
    }

    @Override
    public void onItemSelected(int position) {
        // Do something
        System.out.println("Item " + postion + " selected")
    }
}

CustomPagerAdapter.java

public class CustomPagerAdapter {
    private static final int DEFAULT_POSITION = 1;
    public CustomPagerAdapter(Callback callback) {
        callback.onItemSelected(DEFAULT_POSITION);
    }
}

1

Я недавно начал делать что-то вроде этого:

public class Main {
    @FunctionalInterface
    public interface NotDotNetDelegate {
        int doSomething(int a, int b);
    }

    public static void main(String[] args) {
        // in java 8 (lambdas):
        System.out.println(functionThatTakesDelegate((a, b) -> {return a*b;} , 10, 20));

    }

    public static int functionThatTakesDelegate(NotDotNetDelegate del, int a, int b) {
        // ...
        return del.doSomething(a, b);
    }
}

1

он немного староват, но тем не менее ... Я нашел ответ Питера Уилкинсона хорошим, за исключением того факта, что он не работает для примитивных типов, таких как int / Integer. Проблема в .getClass()том parameters[i], что возвращает, например java.lang.Integer, который, с другой стороны, не будет правильно интерпретироватьсяgetMethod(methodName,parameters[]) (ошибка Java) ...

Я совместил это с предложением Даниэля Спевака ( в его ответе на это ); Шаги к успеху включали: перехват NoSuchMethodException-> getMethods()-> поиск подходящего с помощью method.getName()->, а затем явный цикл по списку параметров и применение решения Дэниелса, например, определение совпадений типов и совпадений сигнатур.


0

Я думаю, что использование абстрактного класса более элегантно, например:

// Something.java

public abstract class Something {   
    public abstract void test();        
    public void usingCallback() {
        System.out.println("This is before callback method");
        test();
        System.out.println("This is after callback method");
    }
}

// CallbackTest.java

public class CallbackTest extends Something {
    @Override
    public void test() {
        System.out.println("This is inside CallbackTest!");
    }

    public static void main(String[] args) {
        CallbackTest myTest = new CallbackTest();
        myTest.usingCallback();
    }    
}

/*
Output:
This is before callback method
This is inside CallbackTest!
This is after callback method
*/

Это полиморфизм, а не обратный вызов. Вам нужно проверить шаблон обратного вызова.
simo.3792

Да, я использую полиморфизм для обратного вызова из суперкласса. Вы не получаете эту функциональность просто полиморфизмом. Я звоню с использованием метода CALLBACK (), который находится в классе Something, а затем он обращается к подклассу, где пользователь написал свою функцию test ().
avatar1337

2
Обратные вызовы предназначены для того, objectчтобы один «уведомлял» другого, objectкогда произошло что-то существенное, поэтому второй objectможет обработать событие. Ваше abstract classникогда не является отдельным object, это только отдельное class. Вы используете только один objectи становитесь разными classesдля выполнения разных функций, что является сущностью полиморфизма, а не шаблона обратного вызова.
simo.3792

0
public class HelloWorldAnonymousClasses {

    //this is an interface with only one method
    interface HelloWorld {
        public void printSomething(String something);
    }

    //this is a simple function called from main()
    public void sayHello() {

    //this is an object with interface reference followed by the definition of the interface itself

        new HelloWorld() {
            public void printSomething(String something) {
                System.out.println("Hello " + something);
            }
        }.printSomething("Abhi");

     //imagine this as an object which is calling the function'printSomething()"
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
                new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }
}
//Output is "Hello Abhi"

В принципе, если вы хотите создать объект интерфейса, это невозможно, потому что интерфейс не может иметь объекты.

Опция заключается в том, чтобы позволить некоторому классу реализовать интерфейс, а затем вызвать эту функцию, используя объект этого класса. Но этот подход действительно многословен.

В качестве альтернативы, напишите новый HelloWorld () (* oberserve, это интерфейс, а не класс), а затем выполните определение самих методов интерфейса. (* Это определение на самом деле является анонимным классом). Затем вы получаете ссылку на объект, через который вы можете вызвать сам метод.


0

Создайте интерфейс и создайте то же свойство интерфейса в классе обратного вызова.

interface dataFetchDelegate {
    void didFetchdata(String data);
}
//callback class
public class BackendManager{
   public dataFetchDelegate Delegate;

   public void getData() {
       //Do something, Http calls/ Any other work
       Delegate.didFetchdata("this is callbackdata");
   }

}

Теперь в классе, к которому вы хотите получить обратный вызов, реализуйте созданный выше интерфейс. а также Передайте «this» объект / ссылку вашего класса для повторного вызова.

public class Main implements dataFetchDelegate
{       
    public static void main( String[] args )
    {
        new Main().getDatafromBackend();
    }

    public void getDatafromBackend() {
        BackendManager inc = new BackendManager();
        //Pass this object as reference.in this Scenario this is Main Object            
        inc.Delegate = this;
        //make call
        inc.getData();
    }

    //This method is called after task/Code Completion
    public void didFetchdata(String callbackData) {
        // TODO Auto-generated method stub
        System.out.println(callbackData);
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.