Funções de retorno de chamada em Java


177

Existe uma maneira de passar uma função de retorno de chamada em um método Java?

O comportamento que estou tentando imitar é um .Net Delegate sendo passado para uma função.

Eu já vi pessoas sugerindo a criação de um objeto separado, mas isso parece exagero, no entanto, estou ciente de que, às vezes, exagero é a única maneira de fazer as coisas.


51
É um exagero porque o Java não é funcional (isso deveria ser um trocadilho ...).
precisa

Respostas:


155

Se você quer dizer algo como o delegado anônimo do .NET, acho que a classe anônima do Java também pode ser usada.

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
Este é o método canônico desde o Java 1.0.
10139 Charlie Martin

1
Eu já usei isso, é um pouco mais detalhado do que eu gostaria, mas funciona.
Omar Kooheji

23
@ Omar, concordou. Voltei ao Java depois de um longo período com C # e sinto muita falta de lambdas / delegados. Venha Java!
Drew Noakes

4
@DrewNoakes, boa notícia é, Java 8 tem lambdas (principalmente) ...
Lucas

2
desde que você definiu a interface Visitor com um único método, você pode transmitir lambdas que correspondem ao invés dele.
Joey Baruch

43

Desde o Java 8, existem referências lambda e método:

Por exemplo, se você deseja uma interface funcional A -> Bcomo:

import java.util.function.Function;

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

então você pode chamar assim

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

Além disso, você pode passar o método de classe. Digamos que você tenha:

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

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

Então você pode fazer:

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

Nota :

Aqui eu usei a interface funcional Function<A,B>, mas há muitas outras no pacote java.util.function. Os mais notáveis ​​são

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

e muitos outros que se especializam em alguns tipos de entrada / saída. Então, se ele não fornecer o necessário, você poderá criar sua própria interface funcional da seguinte maneira:

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

Exemplo de uso:

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

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

1
Para os CallBacks, seria melhor escrever sua própria interface funcional, pois cada tipo de retorno de chamada normalmente teria várias sintaxes.
Sri Harsha Chilakapati

Não tenho certeza de entender. Se uma das classes java.util.functioné o que você está procurando, então você está pronto. Então você pode jogar com genéricos para a E / S. (?)
Juh_

Você não me entendeu, o que eu disse é sobre a tradução de código C ++ que usa funções de retorno de chamada para Java 8. Lá, para cada ponteiro de função exclusivo, você deve criar uma interface Funcional em Java, pois haverá mais parâmetros reais código de produção.
Sri Harsha Chilakapati

2
Minha conclusão é que na maioria das vezes, você precisa escrever suas próprias interfaces funcionais, pois as padrão fornecidas java.util.functionnão são suficientes.
Sri Harsha Chilakapati

1
Eu adicionei uma nota em interfaces funcionais existentes e como criar novos
Juh_

28

Para simplificar, você pode usar um Runnable :

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

Uso:

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

2
Gosto disso porque não preciso criar uma nova interface ou classe apenas para fazer um retorno de chamada simples. Obrigado pela dica!
Bruce

1
public interface SimpleCallback { void callback(Object... objects); }Isso é bastante direto e pode ser útil, você também precisa passar por alguns parâmetros.
Marco C.

@cprcrack nenhuma maneira de passar um valor na função run ()?
22617 Chris Chevalier

16

Um pouco de nitpicking:

Eu pareço pessoas sugerindo a criação de um objeto separado, mas isso parece um exagero

A passagem de um retorno de chamada inclui a criação de um objeto separado em praticamente qualquer linguagem OO, portanto, dificilmente pode ser considerado um exagero. O que você provavelmente quer dizer é que, em Java, é necessário criar uma classe separada, que é mais detalhada (e consome mais recursos) do que nas linguagens com funções ou fechamentos explícitos de primeira classe. No entanto, classes anônimas pelo menos reduzem a verbosidade e podem ser usadas em linha.


12
Sim, foi isso que eu quis dizer. Com mais ou menos 30 eventos, você termina com 30 aulas.
Omar Kooheji

16

no entanto, vejo que há a maneira mais preferida, que era o que eu estava procurando ... é basicamente derivado dessas respostas, mas eu tive que manipulá-lo para mais redundante e eficiente .. e acho que todo mundo procurando o que eu acho

Ao ponto::

primeiro faça uma interface tão simples

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

Agora, para fazer com que esse retorno de chamada seja executado sempre que desejar, para lidar com os resultados - mais provavelmente após uma chamada assíncrona e você deseja executar algumas coisas que dependem dessas reutilizações

// 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 é a função que leva algum tempo em que você deseja adicionar um retorno de chamada para notificá-lo quando os resultados vierem, adicione a interface de retorno de chamada como parâmetro a esse método

espero que meu ponto seja claro, aproveite;)


11

Isso é muito fácil no Java 8 com lambdas.

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

Será assim em caso de discussão? pastebin.com/KFFtXPNA
Nashev 19/06

1
Sim. Qualquer quantidade de argumentos (ou nenhum) funcionará enquanto a sintaxe lambda apropriada for mantida.
gk bwg rhb mbreafteahbtehnb

7

Achei interessante a ideia de implementar usando a biblioteca reflect e cheguei a isso que acho que funciona muito bem. O único lado negativo é perder a verificação do tempo de compilação de que você está passando parâmetros válidos.

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

Você usa assim

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

Parece um pouco como um exagero (/ me patos)
Diederik

depende do número de uso e do tamanho do projeto: exagero para um projeto de poucas classes, prático em um grande.
6133 Juh_

Além disso, dê uma olhada na resposta de @monnoo
Juh_

5

Um método ainda não é um objeto de primeira classe em Java; você não pode passar um ponteiro de função como retorno de chamada. Em vez disso, crie um objeto (que geralmente implementa uma interface) que contém o método necessário e passe-o.

Propostas de fechamento em Java - que forneceriam o comportamento que você procura - foram feitas, mas nenhuma será incluída na próxima versão do Java 7.


1
"Um método não é (ainda) um objeto de primeira classe em Java" - bem, existe a classe de método [1], da qual você certamente pode repassar instâncias. Não é o código OO limpo e idiomático que você esperaria do java, mas pode ser conveniente. Certamente algo a considerar, no entanto. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

4

Quando preciso desse tipo de funcionalidade em Java, geralmente uso o padrão Observer . Isso implica em um objeto extra, mas acho que é um caminho limpo, e é um padrão amplamente compreendido, que ajuda na legibilidade do código.



3

Eu tentei usar java.lang.reflect para implementar 'callback', aqui está um exemplo:

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

Como você passa nulo como um argumento?
TWIStErRob 17/02

@TWiStErRob, neste exemplo, seria como timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. saída seránull test: [null]
LiuYan

NPE não seria args[i].getClass()? Meu argumento é que não funcionará se você selecionar o método com base nos tipos de argumento. Funciona com String.format, mas pode não funcionar com outra coisa que aceite null.
TWIStErRob 17/02

@TWiStErRob, bom ponto! Eu adicionei uma função que pode passar manualmente a argTypesmatriz, para que agora possamos passar o nullargumento / parâmetro sem a ocorrência de NullPointerException. Exemplo de saída:NullParameterTest: String parameter=null, int parameter=888
LiuYan

3

Você também pode fazer o Callbackuso do Delegatepadrão:

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

Recentemente, comecei a fazer algo assim:

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

é um pouco antigo, mas, no entanto ... achei a resposta de Peter Wilkinson agradável, exceto pelo fato de que não funciona para tipos primitivos como int / Integer. O problema é .getClass()for parameters[i], que retorna por exemplo java.lang.Integer, que por outro lado não será corretamente interpretado por getMethod(methodName,parameters[])(falha de Java) ...

Combinei-o com a sugestão de Daniel Spiewak ( em sua resposta a isso ); os passos para o sucesso incluíam: pegar NoSuchMethodException-> getMethods()-> procurar o correspondente por method.getName()-> e, em seguida, percorrer explicitamente a lista de parâmetros e aplicar a solução Daniels, como identificar as correspondências de tipo e de assinatura.


0

Eu acho que usar uma classe abstrata é mais elegante, assim:

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

Isso é polimorfismo, não um retorno de chamada. Você precisa verificar o padrão de retorno de chamada.
precisa saber é o seguinte

Sim, estou usando polimorfismo para retorno de chamada da super classe. Você não obtém essa funcionalidade simplesmente por polimorfismo. Eu estou chamando usingCallback () que está na classe Something, então ele chama de volta para a subclasse onde o usuário escreveu sua função test ().
avatar1337 21/01

2
Os retornos de chamada são projetados para um object"notificar" outro objectquando algo significativo ocorreu, para que o segundo objectpossa manipular o evento. Você abstract classnunca é um separado object, é apenas um separado class. Você está usando apenas um objecte se diferenciando classespara executar funções diferentes, que é a essência do polimorfismo, não o padrão de retorno de chamada.
precisa saber é o seguinte

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"

Basicamente, se você deseja criar o objeto de uma interface, isso não é possível, porque a interface não pode ter objetos.

A opção é permitir que alguma classe implemente a interface e depois chame essa função usando o objeto dessa classe. Mas essa abordagem é realmente detalhada.

Como alternativa, escreva new HelloWorld () (* observe que essa é uma interface, não uma classe) e siga-a com a definição dos métodos da interface. (* Esta definição é na realidade a classe anônima). Então você obtém a referência do objeto através da qual você pode chamar o próprio método.


0

Crie uma interface e crie a mesma propriedade de interface na classe de retorno de chamada.

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

}

Agora na classe em que você deseja ser chamado de volta, implemente a Interface criada acima. e também passe "este" Objeto / Referência da sua classe para ser chamado de volta.

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);
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.