Funkcje zwrotne w Javie


177

Czy istnieje sposób przekazania funkcji wywołania zwrotnego w metodzie Java?

Zachowanie, które próbuję naśladować, to przekazywanie delegata .Net do funkcji.

Widziałem, jak ludzie sugerowali stworzenie oddzielnego obiektu, ale wydaje się to przesada, jednak jestem świadomy, że czasami przesada jest jedynym sposobem na zrobienie czegoś.


51
To przesada, ponieważ Java nie działa (to ma być gra słów ...).
Keith Pinson

Odpowiedzi:


155

Jeśli masz na myśli coś w rodzaju anonimowego delegata .NET, myślę, że można również użyć anonimowej klasy Javy.

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
Jest to metoda kanoniczna od wersji Java 1.0.
Charlie Martin

1
Używałem tego, jest nieco bardziej szczegółowe niż to, co bym chciał, ale działa.
Omar Kooheji

23
@Omar, zgodził się. Wróciłem do Javy po długiej przerwie w C # i naprawdę tęsknię za lambdami / delegatami. Chodź, Java!
Drew Noakes

4
@DrewNoakes, dobra wiadomość jest taka, Java 8 ma (głównie) ...
Lucas

2
ponieważ zdefiniowałeś interfejs Visitor za pomocą jednej metody, możesz zamiast tego przekazywać pasujące lambdy.
Joey Baruch

43

Od wersji Java 8 istnieją odwołania do lambda i metod:

Na przykład, jeśli potrzebujesz funkcjonalnego interfejsu, A -> Btakiego jak:

import java.util.function.Function;

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

możesz to tak nazwać

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

Możesz również przekazać metodę klasową. Powiedz, że masz:

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

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

Następnie możesz:

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

Uwaga :

Tutaj użyłem funkcjonalnego interfejsu Function<A,B> , ale w pakiecie jest wiele innych java.util.function. Najbardziej godne uwagi są

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

i wiele innych, które specjalizują się w niektórych typach wejścia / wyjścia. Następnie, jeśli nie zapewnia tego, którego potrzebujesz, możesz stworzyć własny funkcjonalny interfejs, taki jak:

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

Przykład użycia:

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

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

1
W przypadku CallBack byłoby lepiej napisać własny interfejs funkcjonalny, ponieważ każdy typ wywołania zwrotnego miałby zwykle wiele składni.
Sri Harsha Chilakapati

Nie jestem pewien, czy rozumiem. Jeśli java.util.functionszukasz czegoś, czego szukasz, to możesz iść. Następnie możesz bawić się typami dla I / O. (?)
Juh_

Nie dostałeś mnie, powiedziałem o tłumaczeniu kodu C ++, który wykorzystuje funkcje zwrotne do Java 8, Tam, dla każdego unikalnego wskaźnika funkcji, musisz utworzyć interfejs funkcjonalny w Javie, ponieważ w rzeczywistości będzie więcej parametrów kod produkcji.
Sri Harsha Chilakapati

2
Mój wniosek jest taki, że przez większość czasu musisz napisać własne interfejsy funkcjonalne, ponieważ domyślne dostarczone w programie java.util.functionnie są wystarczające.
Sri Harsha Chilakapati

1
Dodałem notatkę o istniejących interfejsach funkcjonalnych i sposobie tworzenia nowych
1919

28

Dla uproszczenia możesz użyć Runnable :

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

Stosowanie:

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

2
Podoba mi się to, ponieważ nie muszę tworzyć nowego interfejsu ani klasy, aby wykonać proste wywołanie zwrotne. Dzięki za wskazówkę!
Bruce,

1
public interface SimpleCallback { void callback(Object... objects); }Jest to dość proste i może być przydatne, ponieważ musisz również przekazać kilka parametrów.
Marco C.

@cprcrack nie ma możliwości przekazania wartości w funkcji run ()?
Chris Chevalier

16

Trochę czepiania się:

Wydaje mi się, że ludzie sugerują stworzenie oddzielnego obiektu, ale wydaje się to przesada

Przekazywanie wywołania zwrotnego obejmuje utworzenie oddzielnego obiektu w prawie każdym języku obiektowym, więc nie można go uznać za przesadę. Prawdopodobnie masz na myśli to, że w Javie wymaga to utworzenia oddzielnej klasy, która jest bardziej szczegółowa (i wymaga więcej zasobów) niż w językach z jawnymi funkcjami lub zamknięciami pierwszej klasy. Jednak klasy anonimowe przynajmniej zmniejszają szczegółowość i mogą być używane w tekście.


12
Tak, o to mi chodziło. Mając około 30 wydarzeń, otrzymasz 30 klas.
Omar Kooheji

16

jednak widzę, że jest najbardziej preferowany sposób, którego szukałem. Zasadniczo pochodzi z tych odpowiedzi, ale musiałem go zmanipulować, aby był bardziej zbędny i skuteczny .. i myślę, że wszyscy szukają tego, co wymyśliłem

Do momentu::

najpierw uczyń interfejs tak prostym

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

teraz, aby to wywołanie zwrotne działało, kiedy tylko zechcesz, aby obsłużyć wyniki - bardziej prawdopodobne po wywołaniu asynchronicznym i chcesz uruchomić kilka rzeczy, które zależą od tych reusltów

// 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 to funkcja, która zajmuje trochę czasu, gdy chcesz dodać do niej callback, aby powiadomić Cię o wynikach, dodaj interfejs wywołania zwrotnego jako parametr do tej metody

mam nadzieję, że mój punkt widzenia jest jasny, baw się dobrze


11

Jest to bardzo łatwe w Javie 8 z lambdami.

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

Czy tak będzie w przypadku kłótni? pastebin.com/KFFtXPNA
Nashev

1
Tak. Dowolna liczba argumentów (lub brak) będzie działać, o ile zostanie zachowana właściwa składnia lambda.
gk bwg rhb mbreafteahbtehnb

7

Pomysł implementacji za pomocą biblioteki reflektów wydał mi się interesujący i wpadłem na pomysł, który moim zdaniem działa całkiem nieźle. Jedyną wadą jest utrata czasu kompilacji sprawdzania, czy przekazujesz prawidłowe parametry.

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

Używasz tego w ten sposób

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

Wygląda trochę jak overkill (/ me ducks)
Diederik

zależy od ilości zastosowań i wielkości projektu: przesada dla projektu kilkuklasowego, praktyczny dla dużego.
Juh_

Spójrz także na odpowiedź @monnoo
Juh_

5

Metoda nie jest (jeszcze) obiektem pierwszej klasy w Javie; nie możesz przekazać wskaźnika funkcji jako wywołania zwrotnego. Zamiast tego utwórz obiekt (który zwykle implementuje interfejs) zawierający potrzebną metodę i przekaż go.

Propozycje zamknięć w Javie - które zapewniłyby zachowanie, którego szukasz - zostały zaproponowane, ale żadna nie zostanie uwzględniona w nadchodzącej wersji Java 7.


1
„Metoda nie jest (jeszcze) obiektem pierwszej klasy w Javie” - no cóż, istnieje metoda class [1], której z pewnością można przekazywać między instancjami. To nie jest czysty, idiomatyczny kod OO, jakiego można oczekiwać od Java, ale może być celowy. Jednak z pewnością coś do rozważenia. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

4

Kiedy potrzebuję tego rodzaju funkcjonalności w Javie, zwykle używam wzorca Observer . Sugeruje dodatkowy obiekt, ale myślę, że jest to prosta droga i jest to szeroko rozumiany wzorzec, który pomaga w czytelności kodu.



3

Próbowałem użyć java.lang.reflect do zaimplementowania 'callback', oto próbka:

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

Jak przekazać null jako argument?
TWiStErRob

@TWiStErRob, w tym przykładzie byłoby tak timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. wyjście będzienull test: [null]
LiuYan 刘 研

Czy nie byłoby włączone NPE args[i].getClass()? Chodzi mi o to, że nie działa, jeśli wybierzesz metodę opartą na typach argumentów. Działa z String.formatczymś innym, co akceptuje, ale może nie działać null.
TWiStErRob

@TWiStErRob, dobra uwaga! Dodałem funkcję, która może ręcznie przekazywać argTypestablicę, więc teraz możemy przekazać nullargument / parametr bez wystąpienia NullPointerException. Przykładowe dane wyjściowe:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研

3

Możesz również zrobić to Callbackza pomocąDelegate wzoru:

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

Niedawno zacząłem robić coś takiego:

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

jest trochę stary, ale mimo wszystko ... Odpowiedź Petera Wilkinsona wydała mi się przyjemna, poza tym, że nie działa dla typów prymitywnych, takich jak int / Integer. Problemem jest na przykład .getClass()for the parameters[i], który powraca java.lang.Integer, co z drugiej strony nie zostanie poprawnie zinterpretowane przezgetMethod(methodName,parameters[]) (błąd Javy) ...

Połączyłem to z sugestią Daniela Śpiewaka ( w jego odpowiedzi ); kroki do sukcesu obejmowały: wyłapywanie NoSuchMethodException-> getMethods()-> szukanie dopasowania jeden po method.getName()->, a następnie jawne przejrzenie listy parametrów i zastosowanie rozwiązania Daniels, takie jak identyfikacja pasujących typów i pasujących sygnatur.


0

Myślę, że użycie klasy abstrakcyjnej jest bardziej eleganckie, na przykład:

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

To jest polimorfizm, a nie wywołanie zwrotne. Musisz sprawdzić wzorzec oddzwonienia.
simo.3792

Tak, używam polimorfizmu do wywołania zwrotnego z klasy super. Nie uzyskujesz tej funkcjonalności po prostu przez polimorfizm. Wywołuję usingCallback (), który jest w klasie Coś, a następnie wraca do podklasy, w której użytkownik napisał swoją funkcję test ().
avatar1337

2
Wywołania zwrotne mają na objectcelu „powiadomienie” drugiej osoby, objectgdy wydarzy się coś ważnego, aby druga osoba objectmogła obsłużyć to zdarzenie. Twoje abstract classnigdy nie jest oddzielne object, jest tylko oddzielne class. Używasz tylko jednego objecti stajesz się inny classesdo wykonywania różnych funkcji, co jest istotą polimorfizmu, a nie wzorcem wywołania zwrotnego.
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"

Zasadniczo, jeśli chcesz utworzyć obiekt interfejsu, nie jest to możliwe, ponieważ interfejs nie może mieć obiektów.

Opcją jest pozwolić jakiejś klasie zaimplementować interfejs, a następnie wywołać tę funkcję przy użyciu obiektu tej klasy. Ale to podejście jest naprawdę rozwlekłe.

Alternatywnie, napisz new HelloWorld () (* obserwuj, że to jest interfejs, a nie klasa), a następnie zdefiniuj same metody interfejsu. (* Ta definicja jest w rzeczywistości klasą anonimową). Następnie otrzymasz odwołanie do obiektu, za pomocą którego możesz wywołać samą metodę.


0

Utwórz interfejs i utwórz tę samą właściwość interfejsu w klasie wywołania zwrotnego.

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

}

Teraz w klasie, w której chcesz zostać wywołany, zaimplementuj powyższy utworzony interfejs. a także Przekaż „ten” obiekt / referencję swojej klasy, aby został wywołany z powrotem.

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);
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.