Fungsi panggilan balik di Jawa


177

Apakah ada cara untuk melewatkan fungsi panggilan balik dalam metode Java?

Perilaku yang saya coba tiru adalah Delegasi .Net diteruskan ke suatu fungsi.

Saya telah melihat orang-orang menyarankan untuk membuat objek terpisah tetapi itu tampaknya berlebihan, namun saya sadar bahwa kadang-kadang berlebihan adalah satu-satunya cara untuk melakukan sesuatu.


51
Ini berlebihan karena Java tidak berfungsi (itu seharusnya menjadi permainan kata-kata ...).
Keith Pinson

Jawaban:


155

Jika Anda bermaksud sesuatu seperti delegasi .NET anonim, saya pikir kelas anonim Java dapat digunakan juga.

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
Ini adalah metode kanonik sejak Java 1.0.
Charlie Martin

1
Saya sudah menggunakan ini, ini slioghtly lebih bertele-tele dari apa yang saya inginkan, tetapi berhasil.
Omar Kooheji

23
@ Umar, setuju. Saya sudah kembali ke Jawa setelah lama bekerja dengan C # dan sangat merindukan lambdas / delegasi. Ayo Jawa!
Drew Noakes

4
@DrewNoakes, kabar baiknya adalah, Java 8 memiliki lambdas (kebanyakan) ...
Lucas

2
karena Anda telah mendefinisikan Pengunjung antarmuka dengan metode tunggal, Anda dapat melewati lambdas yang cocok sebagai pengganti.
Joey Baruch

43

Sejak Java 8, ada lambda dan referensi metode:

Misalnya, jika Anda menginginkan antarmuka fungsional A -> Bseperti:

import java.util.function.Function;

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

maka Anda bisa menyebutnya seperti itu

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

Anda juga dapat melewati metode kelas. Katakanlah Anda memiliki:

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

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

Maka Anda dapat melakukan:

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

Catatan :

Di sini saya menggunakan antarmuka fungsional Function<A,B>, tetapi ada banyak lainnya dalam paket java.util.function. Yang paling terkenal adalah

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

dan banyak lainnya yang berspesialisasi pada beberapa tipe input / output. Kemudian, jika tidak menyediakan yang Anda butuhkan, Anda dapat membuat antarmuka fungsional Anda sendiri seperti:

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

Contoh penggunaan:

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

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

1
Untuk CallBacks, akan lebih baik untuk menulis antarmuka fungsional Anda sendiri karena setiap jenis panggilan balik biasanya akan memiliki beberapa sintaks.
Sri Harsha Chilakapati

Saya tidak yakin untuk mengerti. Jika salah satu kelas java.util.functionadalah yang Anda cari, maka Anda bisa melanjutkan. Kemudian Anda bisa bermain dengan obat generik untuk I / O. (?)
Juh_

Anda tidak mengerti, apa yang saya katakan adalah tentang menerjemahkan kode C ++ yang menggunakan fungsi callback ke Java 8, Di sana, untuk setiap pointer fungsi yang unik, Anda harus membuat antarmuka Fungsional di Jawa, karena akan ada lebih banyak parameter dalam real kode produksi.
Sri Harsha Chilakapati

2
Kesimpulan saya adalah bahwa sebagian besar waktu, Anda diharuskan untuk menulis antarmuka fungsional Anda sendiri karena yang disediakan standar, di java.util.functiontidak cukup.
Sri Harsha Chilakapati

1
Saya menambahkan catatan pada antarmuka fungsional yang ada dan cara membuat yang baru
Juh_

28

Untuk kesederhanaan, Anda dapat menggunakan Runnable :

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

Pemakaian:

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

2
Saya suka ini karena saya tidak perlu membuat antarmuka atau kelas baru hanya untuk melakukan panggilan balik sederhana. Terima kasih atas tipnya!
Bruce

1
public interface SimpleCallback { void callback(Object... objects); }Ini cukup mudah dan bisa berguna Anda harus melewati beberapa params juga.
Marco C.

@cprcrack tidak ada cara untuk melewatkan nilai dalam fungsi run ()?
Chris Chevalier

16

Sedikit nitpicking:

Saya sepertinya orang menyarankan untuk membuat objek terpisah tapi itu tampaknya berlebihan

Melewati panggilan balik termasuk membuat objek terpisah dalam hampir semua bahasa OO, sehingga hampir tidak dapat dianggap berlebihan. Apa yang mungkin Anda maksudkan adalah bahwa di Jawa, itu mengharuskan Anda untuk membuat kelas terpisah, yang lebih bertele-tele (dan lebih banyak sumber daya) daripada dalam bahasa dengan fungsi atau penutup kelas satu yang eksplisit. Namun, kelas anonim setidaknya mengurangi verbositas dan dapat digunakan inline.


12
Ya itu yang saya maksud. Dengan 30 atau lebih acara Anda berakhir dengan 30 kelas.
Omar Kooheji

16

namun saya melihat ada cara yang paling disukai yaitu apa yang saya cari .. pada dasarnya berasal dari jawaban ini tetapi saya harus memanipulasinya untuk lebih mubazir dan efisien .. dan saya pikir semua orang mencari apa yang saya cari.

Ke titik::

pertama buat Antarmuka yang sederhana

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

sekarang untuk membuat panggilan balik ini berjalan kapan pun Anda ingin lakukan untuk menangani hasil - lebih mungkin setelah panggilan async dan Anda ingin menjalankan beberapa hal yang tergantung pada penggunaan kembali ini

// 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 adalah fungsi yang membutuhkan waktu Anda ingin menambahkan panggilan balik untuk memberi tahu Anda ketika hasilnya datang, tambahkan antarmuka panggilan balik sebagai parameter untuk metode ini

harap poin saya jelas, selamat menikmati;)


11

Ini sangat mudah di Jawa 8 dengan 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();
    }
}

Apakah akan seperti ini dalam kasus dengan argumen? pastebin.com/KFFtXPNA
Nashev

1
Ya. Sejumlah argumen (atau tidak ada) akan berfungsi selama sintaks lambda yang tepat dipertahankan.
gk bwg rhb mbreafteahbtehnb

7

Saya menemukan ide menerapkan menggunakan perpustakaan refleksi menarik dan muncul dengan ini yang saya pikir berfungsi dengan baik. Satu-satunya sisi adalah kehilangan waktu kompilasi memeriksa bahwa Anda melewati parameter yang valid.

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

Anda menggunakannya seperti ini

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

Sepertinya agak berlebihan (/ aku bebek)
Diederik

tergantung pada jumlah penggunaan dan ukuran proyek: berlebihan untuk proyek beberapa kelas, praktis pada yang besar.
Juh_

Lihat juga jawaban dari @ monnoo
Juh_

5

Metode bukanlah (belum) objek kelas satu di Jawa; Anda tidak dapat melewatkan pointer fungsi sebagai panggilan balik. Alih-alih, buat objek (yang biasanya mengimplementasikan antarmuka) yang berisi metode yang Anda butuhkan dan berikan itu.

Proposal untuk penutupan di Jawa — yang akan menyediakan perilaku yang Anda cari — telah dibuat, tetapi tidak ada yang akan disertakan dalam rilis Java 7 mendatang.


1
"Suatu metode bukanlah (belum) objek kelas satu di Jawa" - yah, ada kelas metode [1], di mana Anda dapat dengan mudah memberikan turunan instance. Ini bukan kode OO yang bersih, idiomatis, yang Anda harapkan dari java, tetapi mungkin lebih bijaksana. Tentu saja ada sesuatu yang perlu dipertimbangkan. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

4

Ketika saya membutuhkan fungsionalitas semacam ini di Jawa, saya biasanya menggunakan pola Observer . Memang menyiratkan objek tambahan, tapi saya pikir ini cara yang bersih untuk pergi, dan merupakan pola yang dipahami secara luas, yang membantu dengan keterbacaan kode.



3

Saya mencoba menggunakan java.lang.reflect untuk mengimplementasikan 'callback', berikut ini contohnya:

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

Bagaimana Anda memberikan null sebagai argumen?
TWiStErRob

@TWiStErRob, dalam sampel ini, akan seperti timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. output akannull test: [null]
LiuYan 刘 研

Bukankah ini NPE hidup args[i].getClass()? Maksud saya adalah itu tidak berfungsi jika Anda memilih metode berdasarkan tipe argumen. Ini berfungsi dengan baik String.format, tetapi mungkin tidak bekerja dengan hal lain yang menerima null.
TWiStErRob

@TWiStErRob, Poin bagus! Saya telah menambahkan fungsi yang dapat secara manual melewati argTypesarray, jadi sekarang kita dapat meneruskan nullargumen / parameter tanpa terjadi NullPointerException. Contoh hasil:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘 研

3

Anda juga dapat melakukan Callbackmenggunakan Delegatepola:

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

Saya baru saja mulai melakukan sesuatu seperti ini:

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

ini agak lama, tetapi bagaimanapun ... Saya menemukan jawaban Peter Wilkinson bagus kecuali untuk fakta bahwa itu tidak bekerja untuk tipe primitif seperti int / Integer. Masalahnya adalah .getClass()untuk parameters[i], yang mengembalikan misalnya java.lang.Integer, yang di sisi lain tidak akan ditafsirkan dengan benar olehgetMethod(methodName,parameters[]) (kesalahan Java) ...

Saya menggabungkannya dengan saran dari Daniel Spiewak ( dalam jawabannya untuk ini ); langkah-langkah menuju sukses termasuk: menangkap NoSuchMethodException-> getMethods()-> mencari yang cocok dengan method.getName()-> dan kemudian secara eksplisit mengulang daftar parameter dan menerapkan solusi Daniels, seperti mengidentifikasi jenis pencocokan dan pencocokan tanda tangan.


0

Saya pikir menggunakan kelas abstrak lebih elegan, seperti ini:

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

Ini adalah polimorfisme, bukan panggilan balik. Anda perlu memeriksa pola panggilan balik.
simo.3792

Ya saya menggunakan polimorfisme untuk menelepon kembali dari kelas super. Anda tidak mendapatkan fungsi ini hanya dengan polimorfisme. Saya memanggil usingCallback () yang ada di kelas Something, lalu memanggil kembali ke subclass di mana pengguna telah menulis tes fungsinya ().
avatar1337

2
Panggilan balik dirancang untuk satu objectuntuk "memberitahukan" yang lain objectketika sesuatu yang signifikan telah terjadi, sehingga yang kedua objectdapat menangani acara tersebut. Anda abstract classtidak pernah terpisah object, itu hanya terpisah class. Anda hanya menggunakan satu objectdan menjadi berbeda classesuntuk melakukan fungsi yang berbeda, yang merupakan inti dari polimorfisme, bukan pola panggilan balik.
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"

Pada dasarnya jika Anda ingin membuat objek antarmuka tidak mungkin, karena antarmuka tidak dapat memiliki objek.

Pilihannya adalah membiarkan beberapa kelas mengimplementasikan antarmuka dan kemudian memanggil fungsi itu menggunakan objek kelas itu. Tetapi pendekatan ini benar-benar bertele-tele.

Sebagai alternatif, tulis HelloWorld baru () (* oberserve ini bukan antarmuka bukan kelas) dan kemudian tindak lanjuti dengan definisi metode antarmuka itu sendiri. (* Defination ini pada kenyataannya adalah kelas anonim). Kemudian Anda mendapatkan referensi objek di mana Anda dapat memanggil metode itu sendiri.


0

Buat Antarmuka, dan Buat Properti Antarmuka yang Sama di Kelas Callback.

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

}

Sekarang di kelas di mana Anda ingin dipanggil kembali mengimplementasikan Interface Dibuat di atas. dan Juga Lulus "ini" Obyek / Referensi kelas Anda untuk dipanggil kembali.

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);
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.