Java'da geri arama işlevleri


177

Java yönteminde geri arama işlevini iletmenin bir yolu var mı?

Taklit etmeye çalıştığım davranış, bir işleve geçirilen bir .Net Temsilcisi.

İnsanların ayrı bir nesne oluşturmayı önerdiklerini gördüm ama bu aşırıya kaçmış gibi görünüyor, ancak bazen aşırıya kaçmanın bir şeyler yapmanın tek yolu olduğunun farkındayım.


51
Bu aşırı bir iştir çünkü Java işlevsel değildir (bunun bir kelime oyunu olması gerekiyordu ...).
Keith Pinson

Başka bir java 8 örneği: stackoverflow.com/questions/14319787/…
Vadzim

Yanıtlar:


155

.NET anonim delegesi gibi bir şey demek istiyorsanız, Java'nın anonim sınıfının da kullanılabileceğini düşünüyorum.

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
Bu Java 1.0 beri kanonik yöntemdir.
Charlie Martin

1
Bunu kullanıyorum, istediğimden biraz daha ayrıntılı, ama işe yarıyor.
Omar Kooheji

23
@Omar, kabul etti. C # ile uzun bir süre sonra Java'ya geri geldim ve gerçekten lambdas / delegeleri özledim. Java'ya gel!
Drew Noakes

4
@DrewNoakes, iyi haber, Java 8 lambdas (çoğunlukla) var ...
Lucas

2
Ziyaretçi arayüzünü tek bir yöntemle tanımladığınızdan, bunun yerine eşleşen lambdasları geçirebilirsiniz.
Joey Baruch

43

Java 8'den beri lambda ve yöntem referansları vardır:

Örneğin, aşağıdaki A -> Bgibi işlevsel bir arayüz istiyorsanız :

import java.util.function.Function;

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

o zaman böyle diyebilirsiniz

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

Ayrıca sınıf yöntemini geçebilirsiniz. Diyelim ki:

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

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

Sonra şunları yapabilirsiniz:

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

Not :

Burada fonksiyonel arayüzü kullandım Function<A,B>, ancak pakette başka pek çok şey varjava.util.function . En dikkate değer olanlar

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

ve bazı girdi / çıktı türlerinde uzmanlaşan diğerleri. Ardından, ihtiyacınız olanı sağlamazsa, aşağıdaki gibi kendi işlevsel arayüzünüzü oluşturabilirsiniz:

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

Kullanım örneği:

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

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

1
Geri aramalar için, her bir geri arama türü genellikle birden fazla sözdizimine sahip olacağından kendi işlevsel arayüzünüzü yazmak daha iyi olur.
Sri Harsha Chilakapati

Anladığımdan emin değilim. Eğer sınıflardan biri java.util.functionaradığın şeyse, o zaman gitmek iyidir. Sonra I / O için jeneriklerle oynayabilirsiniz. (?)
Juh_

Beni alamadım, söylediğim şey, geri arama işlevlerini Java 8'e kullanan C ++ kodunu çevirmekle ilgili, Orada, her benzersiz işlev işaretçisi için, Java'da bir İşlevsel arabirim oluşturmanız gerekiyor, üretim kodu.
Sri Harsha Chilakapati

2
Sonuç olarak, varsayılan olarak sağlananlar java.util.functionyeterli olmadığından , çoğu zaman kendi fonksiyonel arayüzlerinizi yazmanız gerekir .
Sri Harsha Chilakapati

1
Mevcut fonksiyonel arayüzler ve nasıl yenileri yaratılacağı hakkında bir not ekledim
Juh_

28

Basit olması için bir Runnable kullanabilirsiniz :

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

Kullanımı:

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

2
Sadece basit bir geri arama yapmak için yeni bir arayüz veya sınıf oluşturmaya gerek yok çünkü bunu seviyorum. Bahşiş için teşekkürler!
Bruce

1
public interface SimpleCallback { void callback(Object... objects); }Bu oldukça basittir ve bazı paramları da geçmeniz yararlı olabilir.
Marco

@cprcrack run () işlevinde bir değer iletmenin bir yolu yok mu?
Chris Chevalier

16

Biraz nitpicking:

Ayrı bir nesne oluşturmayı öneren insanlar var gibi görünüyor ama bu aşırıya kaçmış gibi görünüyor

Geri arama iletimi, hemen hemen tüm OO dillerinde ayrı bir nesne oluşturmayı içerir, bu nedenle aşırı doldurma olarak kabul edilemez. Muhtemelen, Java'da, açık birinci sınıf işlevlere veya kapanışlara sahip dillerden daha ayrıntılı (ve daha fazla kaynak yoğun) olan ayrı bir sınıf oluşturmanızı gerektirir. Ancak, anonim sınıflar en azından ayrıntı düzeyini azaltır ve satır içi kullanılabilir.


12
Evet demek istediğim buydu. 30 ya da daha fazla etkinlikle 30 ders alırsınız.
Omar Kooheji

16

henüz ne aradığım en çok tercih edilen yolu olduğunu görüyorum .. temelde bu cevaplardan türetilmiş ama ben daha fazla gereksiz ve verimli manipüle etmek zorunda kaldım .. ve sanırım herkes ben ne bulmak için arıyorum

Diyeceğim şey şu ki::

önce bu kadar basit bir arayüz yapın

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

şimdi bu geri aramanın sonuçları işlemek istediğinizde çalışmasını sağlamak için - async çağrısından sonra daha olasıdır ve bu reuslts'e bağlı bazı şeyler çalıştırmak istiyorsunuz

// 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 sonuçlar geldiğinde size bildirmek için bir geri arama eklemek istediğiniz zaman alan işlevdir, geri arama arayüzünü bu yönteme parametre olarak ekleyin

benim açımdan umut, zevk;)


11

Bu, Java 8'de lambdaslarla çok kolaydır.

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

Tartışma durumunda bu böyle olacak mı? pastebin.com/KFFtXPNA
Nashev

1
Evet. Herhangi bir miktarda argüman (veya hiçbiri), uygun lambda sözdizimi korunduğu sürece işe yarayacaktır.
gk bwg rhb mbreafteahbtehnb

7

Yansıtma kütüphanesini kullanarak uygulama fikrini ilginç buldum ve bence oldukça iyi çalışıyor. Sadece aşağı tarafı geçerli parametreleri geçtiğinizde derleme zamanı denetimini kaybediyor.

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

Böyle kullanıyorsun

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

Overkill (/ me ducks) gibi görünüyor
Diederik

kullanım sayısına ve projenin büyüklüğüne bağlıdır: birkaç sınıflı bir proje için aşırı büyük, büyük bir proje için pratik.
Juh_

Ayrıca @monnoo
Juh_

5

Bir yöntem (henüz) Java'da birinci sınıf bir nesne değildir; işlev işaretçisini geri arama olarak geçiremezsiniz. Bunun yerine, ihtiyacınız olan yöntemi içeren bir nesne (genellikle bir arabirim uygular) oluşturun ve bunu iletin.

Java'da, aradığınız davranışı sağlayacak kapanış teklifleri yapılmıştır, ancak yaklaşan Java 7 sürümüne hiçbiri dahil edilmeyecektir.


1
"Bir yöntem (henüz) Java'da birinci sınıf bir nesne değildir" - peki, [1] yöntem sınıfı vardır, bunların örnekleri arasında kesinlikle geçiş yapabilirsiniz. Java'dan bekleyebileceğiniz temiz, deyimsel, OO kodu değil, ancak uygun olabilir. Kesinlikle dikkate alınması gereken bir şey olsa. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kolker

4

Java'da bu tür bir işleve ihtiyaç duyduğumda genellikle Observer desenini kullanıyorum . Ekstra bir nesne ima ediyor, ancak bence bu temiz bir yol ve kod okunabilirliğine yardımcı olan yaygın olarak anlaşılmış bir model.



3

'Geri arama' uygulamak için java.lang.reflect kullanarak denedim, işte bir örnek:

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

Bir argüman olarak null değerini nasıl geçirirsiniz?
TWiStErRob

@TWiStErRob, bu örnekte şöyle olurdu timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. çıktınull test: [null]
LiuYan 刘 研

NPE açık değil mi args[i].getClass()? Demek istediğim, argüman türlerine göre yöntem seçerseniz işe yaramaz. Kabul eder String.format, ancak kabul eden başka bir şeyle çalışmayabilir null.
TWiStErRob

@TWiStErRob, İyi nokta! El ile argTypesdizi geçebilir bir işlev ekledim , şimdi nullargüman / parametre NullPointerException oluşmadan geçebiliriz . Örnek çıktı:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研

3

Deseni Callbackkullanarak da yapabilirsiniz 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

Son zamanlarda böyle bir şey yapmaya başladım:

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

biraz eski, ama yine de ... Peter Wilkinson'un cevabını int / Integer gibi ilkel tipler için işe yaraması dışında güzel buldum. Sorun .getClass()için parameters[i], bu örneğin döner java.lang.Integerdiğer taraftan tarafından doğru şekilde yorumlanmamalıdır olacaktır,getMethod(methodName,parameters[]) (Java'nın arıza) ...

Daniel Spiewak'ın önerisiyle birleştirdim (buna cevabında ); başarı için adımlar dahil: NoSuchMethodException-> -> getMethods()-> ile eşleşen birini aramak method.getName()ve daha sonra açıkça parametreler listesinde döngü ve tip eşleşmeleri ve imza eşleşmeleri tanımlamak gibi Daniels çözüm uygulamak.


0

Bence soyut bir sınıf kullanmak daha zarif:

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

Bu bir polimorfizm, geri arama değil. Geri arama düzenini kontrol etmeniz gerekir.
simo.3792

Evet süper sınıftan geri çağırmak için polimorfizm kullanıyorum. Bu işlevselliği sadece polimorfizm ile elde edemezsiniz. Sınıf Something sınıfında kullanarak CallCallback () çağırıyorum, sonra kullanıcı işlev sınaması () yazdığı alt sınıfa geri çağırır.
avatar1337

2
Geri aramalar, önemli objectbir objectşey meydana geldiğinde diğerini "bilgilendirmek" için tasarlanmıştır , böylece ikincisi objectolayı işleyebilir. Senin abstract classasla ayrı objectdeğil, sadece ayrı class. Sadece birini kullanıyorsunuz objectve classesgeri çağırma modeli değil, polimorfizmin özü olan farklı işlevleri yerine getirmek için farklılaşıyorsunuz.
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"

Temel olarak bir arabirimin nesnesini yapmak istiyorsanız, bu mümkün değildir, çünkü arabirimin nesneleri olamaz.

Seçenek, bazı sınıfların arabirimi uygulamasına izin vermek ve ardından o sınıfın nesnesini kullanarak bu işlevi çağırmaktır. Ancak bu yaklaşım gerçekten ayrıntılı.

Alternatif olarak, yeni HelloWorld () yazın (* bu bir sınıf değil, bir arabirimdir) ve daha sonra arabirim yöntemlerinin tanımını takip edin. (* Bu tanım gerçekte anonim sınıftır). Ardından, yöntemin kendisini çağırabileceğiniz nesne başvurusunu alırsınız.


0

Bir arabirim oluşturun ve geri arama sınıfında aynı arabirim özelliği oluşturun.

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

}

Şimdi geri çağırmak istediğiniz sınıfta yukarıdaki Oluşturulan Arayüzü uygulayın. ve Geri çağrılacak sınıfınızın "this" Nesnesi / Referansını da iletin.

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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.