Функції зворотного дзвінка на Java


177

Чи є спосіб передати функцію зворотного дзвінка у методі Java?

Поведінка, яку я намагаюся імітувати, - це делегат .Net, який передається функції.

Я бачив людей, які пропонують створити окремий об’єкт, але це здається непосильним, однак я усвідомлюю, що іноді надмірний рівень - це єдиний спосіб робити речі.


51
Це надмірно, оскільки Java нефункціональна (це має бути каламбур ...).
Кіт Пінсон

Інший Java 8 Приклад: stackoverflow.com/questions/14319787 / ...
Vadzim

Відповіді:


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
@Omar, погодився. Я повернувся до Яви після довгого переживання з 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);
}

Тепер, щоб зробити цей зворотний виклик запускати коли завгодно, щоб обробити результати - швидше за все після виклику async, і ви хочете запустити деякі речі, що залежать від цих 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 це функція, яка потребує певного часу, коли ви хочете додати зворотній дзвінок до неї, щоб повідомити вас про результати, додати інтерфейс зворотного дзвінка як параметр цього методу

сподіваюся, моя суть зрозуміла, насолоджуйтесь;)


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

Виглядає дещо як надмір (/ мені качок)
Дідерік

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

Також подивіться відповідь @monnoo
Juh_

5

Метод не є (ще) об'єктом першого класу на Java; ви не можете передавати покажчик функції як зворотний дзвінок. Натомість створіть об’єкт (який зазвичай реалізує інтерфейс), який містить потрібний вам метод, і передайте його.

Пропозиції щодо закриття в Java - які б забезпечували поведінку, яку ви шукаєте - були внесені, але жодна не буде включена до майбутнього випуску Java 7.


1
"Метод не є (ще) об'єктом першого класу на Java" - ну, є клас класів [1], з якого ви, звичайно, можете обходити екземпляри. Ява, який ви очікуєте від Java, не чистий, ідіоматичний код OO, але він може бути доцільним. Звичайно, щось варто врахувати. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

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

Як ви передаєте null як аргумент?
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()->, а потім чітко прокручування списку параметрів та застосування рішення Daniels, таке визначення типу відповідності та відповідності підписів.


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, потім він передзвонює до підкласу, де користувач написав тест своєї функції ().
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 () (* спостерігайте, що це інтерфейс, а не клас), а потім слідкуйте за цим визначенням самих методів інтерфейсу. (* Це визначення насправді є анонімним класом). Потім ви отримуєте посилання на об'єкт, через який ви можете викликати сам метод.


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

}

Тепер у класі, куди потрібно звернутися, реалізуйте вище створений інтерфейс. а також передайте "цей" об'єкт / довідник вашого класу, який потрібно передзвонити.

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.