【问题标题】:Call backs in Java (code explanation)Java中的回调(代码解释)
【发布时间】:2011-05-01 18:56:10
【问题描述】:

我遇到了这个question 关于Java 中的回调。她是正在运行的code 和原始答案here

  1. 但我不明白它对回调有什么用处?
  2. 您能向 Java 程序员解释一下回调的概念吗?

代码:

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

    }
}

【问题讨论】:

    标签: java callback


    【解决方案1】:

    我曾经使用 Java 进行开发,直到我开始使用 C# 及其委托进行编程时,我才完全理解回调的概念。

    这是因为@Denilson Sá 完美地提到 Java 不使用指向函数的指针。 换句话说,在 Java 中,您可以调用方法并传递一些参数,例如原始值(int、long、char、boolean 等)和对象(字符串或任何类的任何实例,因为当您传递对象时,您基本上是传递位于内存某处的真实对象在内存中的地址)。

    Java 中的回调概念可以通过使用接口并将它们(实现它们的对象)作为参数传递来实现。想象一下,您有以下接口,它定义了 2 个方法,任何想要充当 ResultListener 的类都必须实现这些方法。

    interface ResultListener {
       void onSuccessOperation(String description);
       void onFailedOperation(String description);
    }
    

    现在假设您有一个在 showScreen 方法中运行的主程序

    class MyMainScreen implements ResultListener {
       public void showScreen() {
        //do some things..
        SmartClass smartClass = new SmartClass();
        smartClass.divideAndNotify(5, 0, this);
        }
    
        public void onSuccessOperation(String description) {
           System.out.println("SUCCESS!!. " + description);
        }
    
        public void onFailedOperation(String description) {
           System.out.println("FAILED. " + description);
        }
    }
    

    这就是知道如何划分的 SmartClass。

    class SmartClass {
       public void divideAndNotify(int numerador, int denominador, ResultListener resultListener) {
            if (denominador == 0) {
                resultListener.onFailedOperation("Nobody can divide by zero!!");
            } else {
                int total = numerador / denominador;
                resultListener.onSuccessOperation("The result is " + total);
            }
        }
    }    
    

    这里有趣的部分是 MyMainScreen 的行为类似于 ResultListener,因此它需要实现接口 ResultListener 中定义的方法。 MyMainScreen 知道如何在控制台上打印消息,但它对计算一无所知,这就是为什么它实例化 SmartClass 以使用它的方法 divideAndNotify 接受 2 个数字和对将侦听结果的实例的引用(在我们的如果这个实例是 MyMainScreen 实例本身,这就是为什么它用词 this)

    传递自己

    SmartClass 方法 divideAndNotify 知道数学,并会通知正在监听的人操作结果。它的方法知道 resultListener 将包含对一个对象的引用,该对象知道当结果成功或不成功时该做什么。

    这里的回调概念是 SmartClass 将功能委托给如何处理结果,就像“回调”它作为参数接收的实例中的某些内容。

    总而言之:回调只是一个任务的委托。

    PS:对于 C#,这个概念要简单得多,因为 C# 具有委托类型,这些委托类型是存储函数所在内存地址的变量(存储的函数必须与委托中定义的签名匹配)。

    【讨论】:

      【解决方案2】:

      我不想在这里引发一场激烈的战争...但是 回调 的概念在 C/C++、JavaScript、Python 等语言中更容易理解,可能还有 Ruby 等等其他。在此类语言中,回调只是一个函数指针。您将函数作为此函数指针传递,其他代码将使用该指针回调您的函数。就如此容易。 (look at this C example from Wikipedia)

      但是Java没有函数指针,因此Java程序员需要使用匿名内部类接口之类的东西来将函数封装在里面一个类,并将该类的一个实例作为回调传递。

      我想我设法回答了您的第二个问题(“您能向 Java 程序员解释回调的概念吗?”),但请参阅其他有关如何在 Java 中实现回调的答案。

      【讨论】:

        【解决方案3】:

        它们被称为“匿名内部类”。它们基本上是接口/抽象类的实现,而无需编写完整的类。

        编译器实际上会为其中的每一个创建一个类,所以如果你编译上面的代码,你会看到类似:

        Main.class
        Main$Visitor.class
        Main$1.class  <-- This is probably the "adder" implementation
        Main$2.class  <-- This is probably the "multiplier" implementation
        

        这些类的美妙之处在于它们可以从您的方法/类中读取内容,而无需您传递这些参数。例如:

        ...
        final int extraMultiplyer = 10;
        
        Visitor multiplier = new Visitor(){
            public int DoJob(int a, int b) {
                //Note that extraMultiplyer is defined in the main() method
                //and is final.
                return a*b*extraMultiplyer;
            }
        };
        ...
        

        它们对于事件处理(想想 Swing)特别有用,在这种情况下,您通常需要实现 ActionListener 接口,而您需要有一个 actionPerormed() 方法。 如果您有一个带有许多不同控件的 UI 类,那么每当您注册侦听器时,您可能希望注册这些匿名类以:a)使代码(可以说)更具可读性和 b)更容易了解每个组件的作用,而不是拥有巨大的“if-else if-else”风格的 actionPerfomed 实现

        例如:

        //abbreviated code
        button1.addActionListener(new ActionListener(){
            //you do what you need here for the action of pressing this button
        });
        button2.addActionListener(new ActionListener() {
           //you do what you need here for the action of pressing this button
        });
        

        【讨论】:

        • 根据您对其实际操作的评论:创建实现该接口并执行它们的类。与拥有一个访问者接口和 2 个“命名”、“具体”的接口实现类似(但不完全相同,参见帖子),我们称它们为“MultiplierVisitor 实现访问者”和“AdderVisitor 实现访问者”
        【解决方案4】:

        我在某个时候写了一篇关于此的小博客文章:http://madhurtanwani.blogspot.com/2010/09/callbacks-in-java.html。希望对您有所帮助!

        在我尝试解释上面的代码帖子之前,我必须说,它不是最直观或最好的回调用法。我在帖子中使用的示例是 Collections.sort(),它清楚地显示了 callback 部分。

        Neverthelss,对于上面发布的代码,请这样想:

        1. main()函数其实是一个数字(读Data)流解析算法,会解析出一对数字(数据集),然后对它们进行操作。
        2. 现在,考虑到关注点分离,main() 函数不应该知道或理解可以对数据集执行哪些操作。事实上,它甚至不应该关心数据集的数据类型。
        3. 但是,由于这些数据集特定于您的域,因此理想情况下,主函数应该将数据处理委派到您的域特定类。
        4. 为此,它必须与特定于域的调用者(main 的调用者)强制签订合同,然后说 - 看,我将在 Visitor interface 实现上调用 doJob 方法,无论何时我收到一对数据集。调用者必须做的是实现访问者接口并实现特定领域的逻辑来处理数据集。

        调用者被调用者委派处理部分称为callback使用实现Java 中的interface(合约规范)。

        【讨论】:

        • 有趣的阅读,但我也试图理解发布的代码。
        【解决方案5】:

        您说的是我自己从未调用过回调的实体。您正在谈论的实体称为函数指针(如 madhuranwani 所述)/委托/匿名函数/动作。

        这些通常用于自定义通用算法实现(如您提到的 Collections.sort)。

            public class Actions {
            public static void main(String[] args) {
                printingAlgorithm(new Action() {
        
                    public void perform() {
                        System.out.println("CustomAction.perform");
                    }
                });
            }
        
            private static void printingAlgorithm(Action customization) {
                System.out.println("------");
                customization.perform();
                System.out.println("++++++");
            }
        }
        
        interface Action {
            void perform();
        }
        

        在我所在地区通常称为回调的实体更像是侦听器。这是一个例子:

        public class Callbacks {
        
            public static void main(String[] args) {
                printingAlgorithm(new PrintingCallback() {
                    public void printBody() {
                        System.out.println("custom body");
                    }
        
                    public void printHeader() {
                        System.out.println("---------");
                    }
        
                    public void printFooter() {
                        System.out.println("+++++++++");
                    }
                });
            }
        
            private static void printingAlgorithm(PrintingCallback callback) {
                callback.printHeader();
                callback.printBody();
                callback.printFooter();
            }
        }
        
        interface PrintingCallback {
            void printHeader();
        
            void printBody();
        
            void printFooter();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-12-20
          • 1970-01-01
          • 1970-01-01
          • 2017-04-18
          • 1970-01-01
          • 2012-05-04
          • 2016-06-29
          • 1970-01-01
          相关资源
          最近更新 更多