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

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

поведение я пытаюсь имитировать это .Чистая делегат передается в функцию.

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

16 ответов


Если вы имеете в виду что-то вроде анонимного делегата .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));

    }
}

начиная с Java 8, есть ссылки на лямбда и метод:

например, определим:

public class FirstClass {
    String prefix;
    public FirstClass(String prefix){
        this.prefix = prefix;
    }
    public String addPrefix(String suffix){
        return prefix +":"+suffix;
    }
}

и

import java.util.function.Function;

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

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

FirstClass first = new FirstClass("first");
SecondClass second = new SecondClass();
System.out.println(second.applyFunction("second",first::addPrefix));

вы можете найти пример на GitHub, здесь: julien-diener / MethodReference.


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

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

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

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

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

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

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


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

к делу::

сначала сделайте интерфейс это просто

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

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

// 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 это функция, которая занимает некоторое время, вы хотите добавить обратный вызов к нему, чтобы уведомить вас, когда пришли результаты, добавьте интерфейс обратного вызова в качестве параметра к этому методу

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


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

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

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

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


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


Регистрация закрытие как они были реализованы в библиотеке lambdaj. На самом деле они ведут себя очень похоже на делегатов C#:

http://code.google.com/p/lambdaj/wiki/Closures


Это очень легко в 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();
    }
}

Я попытался использовать java.ленг.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 ();
    }
}

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

обратный.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);
    }
}

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

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


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

// 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
*/

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

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

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"

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

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

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