Các hàm gọi lại trong Java


177

Có cách nào để truyền hàm gọi lại trong phương thức Java không?

Hành vi tôi đang cố gắng bắt chước là một .Net Delegate được truyền cho một chức năng.

Tôi đã thấy mọi người đề xuất tạo một đối tượng riêng biệt nhưng điều đó có vẻ quá mức, tuy nhiên tôi nhận thấy rằng đôi khi quá mức là cách duy nhất để làm mọi việc.


51
Đó là quá mức cần thiết bởi vì Java là không chức năng (đó được coi là một sự chơi chữ ...).
Keith Pinson

Một ví dụ khác về java 8: stackoverflow.com/questions/14319787/ từ
Vadzim

Câu trả lời:


155

Nếu bạn có nghĩa là một cái gì đó như đại biểu ẩn danh .NET, tôi nghĩ rằng lớp ẩn danh của Java cũng có thể được sử dụng.

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
Đây là phương thức chính tắc kể từ Java 1.0.
Charlie Martin

1
Tôi đã sử dụng cái này, nó dài hơn nhiều so với những gì tôi muốn, nhưng nó hoạt động.
Omar Kooheji

23
@Omar, đồng ý. Tôi đã trở lại Java sau một thời gian dài với C # và thực sự nhớ lambdas / đại biểu. Cố lên Java!
Drew Noakes

4
@DrewKhông, tin tốt là, Java 8 có lambdas (chủ yếu) ...
Lucas

2
vì bạn đã xác định Giao diện khách truy cập bằng một phương thức duy nhất, bạn có thể vượt qua lambdas phù hợp thay vì nó.
Joey Baruch

43

Kể từ Java 8, có các tham chiếu phương thức và lambda:

Ví dụ: nếu bạn muốn một giao diện chức năng, A -> Bchẳng hạn như:

import java.util.function.Function;

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

sau đó bạn có thể gọi nó như vậy

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

Ngoài ra, bạn có thể vượt qua phương pháp lớp. Nói rằng bạn có:

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

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

Sau đó, bạn có thể làm:

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

Lưu ý :

Ở đây tôi đã sử dụng giao diện chức năng Function<A,B>, nhưng có nhiều cái khác trong gói java.util.function. Những cái đáng chú ý nhất là

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

và nhiều người khác chuyên về một số loại đầu vào / đầu ra. Sau đó, nếu nó không cung cấp thứ bạn cần, bạn có thể tạo giao diện chức năng của riêng mình như sau:

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

Ví dụ sử dụng:

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

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

1
Đối với CallBacks, sẽ tốt hơn khi viết giao diện chức năng của riêng bạn vì mỗi loại gọi lại thường có nhiều cú pháp.
Sri Harsha Chilakapati

Tôi không chắc chắn để hiểu. Nếu một trong những lớp học java.util.functionlà những gì bạn đang tìm kiếm, thì bạn tốt để đi. Sau đó, bạn có thể chơi với tướng cho I / O. (?)
Juh_ 10/12/14

Bạn đã không hiểu tôi, những gì tôi đã nói là về việc dịch mã C ++ sử dụng các hàm gọi lại sang Java 8, ở đó, với mỗi con trỏ hàm duy nhất, bạn phải tạo một giao diện Hàm trong Java, vì sẽ có nhiều tham số hơn trong thực tế Mã sản xuất.
Sri Harsha Chilakapati

2
Kết luận của tôi là hầu hết thời gian, bạn được yêu cầu viết các giao diện chức năng của riêng bạn vì các giao diện được cung cấp mặc định, java.util.functionkhông đủ.
Sri Harsha Chilakapati

1
Tôi đã thêm một ghi chú về các giao diện chức năng hiện có và cách tạo giao diện mới
Juh_

28

Để đơn giản, bạn có thể sử dụng Runnable :

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

Sử dụng:

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

2
Tôi thích điều này bởi vì tôi không cần phải tạo một giao diện hoặc lớp mới chỉ để thực hiện một cuộc gọi lại đơn giản. Cảm ơn vì tiền hỗ trợ!
Bruce

1
public interface SimpleCallback { void callback(Object... objects); }Điều này khá đơn giản và có thể hữu ích, bạn cũng cần phải vượt qua một số thông số.
Marco C.

@cprcrack không có cách nào để vượt qua một giá trị trong hàm run ()?
Chris Chevalier

16

Một chút nitpicking:

Tôi dường như mọi người đề nghị tạo một đối tượng riêng biệt nhưng điều đó dường như quá mức cần thiết

Vượt qua một cuộc gọi lại bao gồm việc tạo một đối tượng riêng biệt bằng bất kỳ ngôn ngữ OO nào, vì vậy nó khó có thể được coi là quá mức cần thiết. Điều bạn có thể muốn nói là trong Java, nó yêu cầu bạn tạo một lớp riêng biệt, dài dòng hơn (và tốn nhiều tài nguyên hơn) so với các ngôn ngữ có hàm hoặc lớp đóng rõ ràng. Tuy nhiên, các lớp ẩn danh ít nhất làm giảm tính dài dòng và có thể được sử dụng nội tuyến.


12
Vâng, đó là những gì tôi muốn nói. Với 30 sự kiện hoặc hơn, bạn kết thúc với 30 lớp.
Omar Kooheji

16

Tuy nhiên, tôi thấy có một cách ưa thích nhất đó là những gì tôi đang tìm kiếm .. về cơ bản nó bắt nguồn từ những câu trả lời này nhưng tôi đã phải điều khiển nó để dư thừa và hiệu quả hơn .. và tôi nghĩ mọi người đang tìm kiếm những gì tôi nghĩ ra

Đến điểm::

Đầu tiên hãy tạo một Giao diện đơn giản.

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

ngay bây giờ để thực hiện cuộc gọi lại này khi bạn muốn thực hiện để xử lý kết quả - nhiều khả năng sau cuộc gọi không đồng bộ và bạn muốn chạy một số nội dung phụ thuộc vào các lần sử dụng lại này

// 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 là chức năng cần một chút thời gian bạn muốn thêm một cuộc gọi lại để thông báo cho bạn khi có kết quả, hãy thêm giao diện gọi lại làm tham số cho phương thức này

hy vọng quan điểm của tôi là rõ ràng, tận hưởng;)


11

Điều này rất dễ dàng trong Java 8 với 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();
    }
}

Nó sẽ như thế này trong trường hợp có tranh luận? pastebin.com/KFFtXPNA
Nashev

1
Vâng. Bất kỳ số lượng đối số (hoặc không có) sẽ hoạt động miễn là cú pháp lambda thích hợp được duy trì.
gk bwg rhb mbreafteahbtehnb

7

Tôi thấy ý tưởng thực hiện bằng cách sử dụng thư viện phản chiếu thú vị và đã đưa ra điều này mà tôi nghĩ rằng nó hoạt động khá tốt. Mặt trái duy nhất là mất kiểm tra thời gian biên dịch rằng bạn đang truyền các tham số hợp lệ.

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ạn sử dụng nó như thế này

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

Trông hơi giống như quá mức cần thiết (/ tôi vịt)
Diederik

phụ thuộc vào số lượng sử dụng và quy mô của dự án: quá mức cần thiết cho một dự án vài lớp, thực tế trên một dự án lớn.
Juh_

Ngoài ra, hãy xem câu trả lời của @monnoo
Juh_

5

Một phương thức không (chưa) là một đối tượng hạng nhất trong Java; bạn không thể vượt qua một con trỏ hàm như một cuộc gọi lại. Thay vào đó, hãy tạo một đối tượng (thường thực hiện một giao diện) có chứa phương thức bạn cần và vượt qua nó.

Các đề xuất về việc đóng cửa trong Java, điều này sẽ cung cấp hành vi mà bạn đang tìm kiếm đã được thực hiện, nhưng không có đề xuất nào được đưa vào bản phát hành Java 7 sắp tới.


1
"Một phương thức chưa (chưa) là một đối tượng hạng nhất trong Java" - tốt, có lớp phương thức [1], trong đó bạn chắc chắn có thể vượt qua các cá thể. Đó không phải là mã OO sạch, thành ngữ mà bạn mong đợi từ java, nhưng nó có thể là phương tiện. Chắc chắn một cái gì đó để xem xét, mặc dù. [1] java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Method.html
Jonas Kölker

4

Khi tôi cần loại chức năng này trong Java, tôi thường sử dụng mẫu Observer . Nó ngụ ý một đối tượng bổ sung, nhưng tôi nghĩ rằng đó là một cách sạch sẽ và là một mô hình được hiểu rộng rãi, giúp dễ đọc mã.



3

Tôi đã thử sử dụng java.lang.reflect để triển khai 'gọi lại', đây là một mẫu:

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

Làm thế nào để bạn vượt qua null như một arg?
TWiStErRob

@TWiStErRob, trong mẫu này, nó sẽ như thế timer.SetTimer ((int)(Math.random ()*10), System.out, "printf", "%s: [%s]", new Object[]{"null test", null});. đầu ra sẽ lànull test: [null]
LiuYan 刘

Nó sẽ không NPE trên args[i].getClass()? Quan điểm của tôi là nó không hoạt động nếu bạn chọn phương thức dựa trên các loại đối số. Nó hoạt động với String.format, nhưng có thể không hoạt động với cái gì khác chấp nhận null.
TWiStErRob

@TWiStErRob, Điểm tốt! Tôi đã thêm một hàm có thể truyền argTypesmảng theo cách thủ công , vì vậy bây giờ chúng ta có thể truyền nulltham số / tham số mà không xảy ra NullPulumException. Đầu ra mẫu:NullParameterTest: String parameter=null, int parameter=888
LiuYan 刘

3

Bạn cũng có thể thực hiện Callbackbằng cách sử dụng Delegatemẫu:

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

CustomPagerAdOG.java

public class CustomPagerAdapter {
    private static final int DEFAULT_POSITION = 1;
    public CustomPagerAdapter(Callback callback) {
        callback.onItemSelected(DEFAULT_POSITION);
    }
}

1

Gần đây tôi đã bắt đầu làm một cái gì đó như thế này:

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

nó hơi cũ, nhưng tuy nhiên ... tôi thấy câu trả lời của Peter Wilkinson rất hay ngoại trừ thực tế là nó không hoạt động đối với các kiểu nguyên thủy như int / Integer. Vấn đề là .getClass()cho parameters[i], mà lợi nhuận chẳng hạn java.lang.Integer, mà mặt khác sẽ không được giải thích một cách chính xác bởi getMethod(methodName,parameters[])(lỗi Java) ...

Tôi đã kết hợp nó với gợi ý của Daniel Spiewak ( trong câu trả lời của anh ấy cho vấn đề này ); bao gồm các bước để thành công: bắt NoSuchMethodException-> getMethods()-> tìm kiếm kết quả khớp bằng cách method.getName()-> và sau đó lặp lại rõ ràng qua danh sách các tham số và áp dụng giải pháp Daniels, như xác định loại khớp và chữ ký khớp.


0

Tôi nghĩ rằng sử dụng một lớp trừu tượng là thanh lịch hơn, như thế này:

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

Đây là đa hình, không phải là một cuộc gọi lại. Bạn cần kiểm tra mẫu gọi lại.
simo.3792

Có, tôi đang sử dụng đa hình để gọi lại từ siêu hạng. Bạn không có được chức năng này đơn giản bằng đa hình. Tôi đang gọi bằng cách sử dụngCallback () trong lớp Một cái gì đó, sau đó nó gọi lại lớp con nơi người dùng đã viết hàm test của nó ().
avatar1337

2
Cuộc gọi lại được thiết kế để một người object"thông báo" cho người khác objectkhi có sự cố quan trọng xảy ra, vì vậy cuộc gọi thứ hai objectcó thể xử lý sự kiện. Của bạn abstract classkhông bao giờ là riêng biệt object, nó chỉ là một riêng biệt class. Bạn chỉ đang sử dụng một objectvà nhận được khác nhau classesđể thực hiện các chức năng khác nhau, đó là bản chất của đa hình, không phải là mẫu gọi lại.
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"

Về cơ bản nếu bạn muốn tạo đối tượng của một giao diện thì không thể, vì giao diện không thể có các đối tượng.

Tùy chọn là để cho một số lớp thực hiện giao diện và sau đó gọi hàm đó bằng cách sử dụng đối tượng của lớp đó. Nhưng cách tiếp cận này thực sự dài dòng.

Ngoài ra, hãy viết HelloWorld () (* mới, đây là một giao diện không phải là một lớp) và sau đó theo dõi nó với sự định nghĩa của chính các phương thức giao diện. (* Sự định nghĩa này trong thực tế là lớp ẩn danh). Sau đó, bạn có được tham chiếu đối tượng thông qua đó bạn có thể gọi chính phương thức đó.


0

Tạo giao diện và tạo thuộc tính giao diện tương tự trong lớp gọi lại.

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

}

Bây giờ trong lớp mà bạn muốn được gọi lại thực hiện Giao diện đã tạo ở trên. và cũng vượt qua "đối tượng" này / Tham chiếu của lớp của bạn để được gọi lại.

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.