码迷,mamicode.com
首页 > 其他好文 > 详细

[14-02] 回调

时间:2018-01-08 12:17:40      阅读:189      评论:0      收藏:0      [点我收藏+]

标签:接口调用   需要   领导   参数   关系   can   时间   例子   cos   


1、我所理解的回调

在查看内部类相关知识点的资料时,总是看到两个关键字:闭包和回调。闭包大概能明白,算是一种程序结构,差不多就是能够访问外部变量的某种“域”,在Java看来也就是内部类了。而回调的话,总是很懵懂,在前端用AJAX知道有这么个东西,但理解不深刻。现在看来,回调大概就是把引用交给别人,由别人在适当的时候调用该引用(这里的引用在Java中往往是对象,在JS中是函数,毕竟JS中函数可以作为对象传递)。你调用别人,即主动调用;别人反过来调用你,就是回调。

在网上四处看了些大概的说法,摘录一些自己比较能够理解的说法
  • 一般写程序是你调用系统的API,如果把关系反过来,你写一个函数,让系统调用你的函数,那就是回调了,那个被系统调用的函数就是回调函数。(https://www.zhihu.com/question/19801131)

  • 其实就是传一段代码给某个方法A,然后方法A可以按照自己的需要在适当的时候执行这段传进来的代码。所有的回调应该都是这么个逻辑。(http://www.cnblogs.com/heshuchao/p/5376298.html

  • 编程上来说,一般使用一个库或类时,是你主动调用人家的API,这个叫Call,有的时候这样不能满足需要,需要你注册(注入)你自己的程序(比如一个对象),然后让人家在合适的时候来调用你,这叫Callback。设计模式中的Observer就是例子(http://blog.csdn.net/yu422560654/article/details/7001797

为什么在闭包的概念里总是提到回调,这是因为Java的闭包中往往要将内部类的引用返回,如 Bar getBar() :
  1. public class Foo {
  2. //成员变量
  3. private int local = 0;
  4. //内部类
  5. class Bar {
  6. public int func() {
  7. local++;
  8. System.out.println(local);
  9. return local;
  10. }
  11. }
  12. //返回一个内部类的引用
  13. public Bar getBar() {
  14. return new Bar();
  15. }
  16. }

内部类的引用交管给别人,由别人在适当的时候调用,这不就是“回调”了嘛。

看一个小小的例子,下例用于打印输出某个方法执行的耗时,通过定义接口的方式实现回调:
  1. public interface Callback {
  2. //执行回调
  3. void execute();
  4. }
  1. public class Tool {
  2. /**
  3. * 测试方法执行的耗时
  4. *
  5. * @param callback 回调方法
  6. */
  7. public static void timeConsume(Callback callback) {
  8. long start = System.currentTimeMillis();
  9. callback.execute();
  10. long end = System.currentTimeMillis();
  11. System.out.println("[time consume]:" + (end - start) + "ms");
  12. }
  13. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Tool.timeConsume(new Callback() {
  4. @Override
  5. //填写你需要测试的方法内容,这里简单写个数字计算的例子
  6. public void execute() {
  7. int result = 0;
  8. for (int i = 0; i < 100000; i++) {
  9. result += i;
  10. }
  11. System.out.println(result);
  12. }
  13. });
  14. }
  15. }

在Test中可以看到,直接调用,传入一个匿名内部类实现方法来完成回调,这实际上和JS中传入函数作为变量已经很相似了。你可能要说,JS中传入的函数变量可以是闭包,那么在Java中也很简单,在某个类中写好固定的内部类并写个返回内部类引用的方法,在此处调用timeConsume()时将该引用传入,就和JS中传入函数变量的形式相同了。

2、面向接口回调

另外,还有一点需要提醒的是,在诸多回调的使用中,都是采用的面向接口编程,让某个类实现该接口,然后传入该接口实现类。那么问题来了,为什么不直接传入对象本身的引用把自己完全暴露给别人,太不安全

假设现在有类Boss,领导有查看所有人工资viewAllSalary,发工资paySalary等;还有一个员工类Employee。好了,现在Boss交代给Employee某件事,要求其完成之后报告给老板,这就是回调了:

  • 如果是面向接口编程,老板要实现TellMeInfo接口,然后实现接口中doThingsWithInfo
  • 回调,那得把自己的引用给员工才行,那么以TellMeInfo的实现类的形式给员工就行了
  • 员工拿到了Boss的引用,但是因为是面向接口,所以只能执行doThingsWithInfo方法

  • 如果我们直接传入对象本身的引用,老板直接写好某个方法doThingsWithInfo
  • Boss要求员工完成工作后,调用这个doThingsWithInfo方法
  • 员工拿到的是对象本身的引用,拿到一看,卧槽,惊呆了,可做的事情太多了
  • 有了这个完整引用,不就可以调用ViewAllSalary查看其他同事的薪资,甚至还能paySalary给自己多发钱
  • 员工富裕了,老板的公司倒闭了,老板没弄明白自己错在哪里

下面来看上面场景的模拟代码,先看面向接口编程:
  1. //回调接口
  2. public interface TellMeInfo {
  3. void doThingsWithInfo(String result);
  4. }
  1. //领导
  2. public class Boss implements TellMeInfo{
  3. public void viewAllSalary() {
  4. //输出所有人的工资表
  5. }
  6. public void paySalary(Employee employee, long salary) {
  7. //给某员工发放薪水
  8. }
  9. @Override
  10. public void doThingsWithInfo(String result) {
  11. System.out.println("boss do other things according to the result:" + result);
  12. }
  13. }
  1. //员工
  2. public class Employee {
  3. public String work() {
  4. String result = "balabala";
  5. return result;
  6. }
  7. public void workAndCallback(TellMeInfo boss) {
  8. String result = work();
  9. boss.doThingsWithInfo(result);
  10. }
  11. }
  1. //测试类:领导让员工做完某事后报告给他,然后他才能根据事情结果去处理其他事情
  2. public class Test {
  3. public static void main(String[] args) {
  4. Boss boss = new Boss();
  5. Employee employee = new Employee();
  6. employee.workAndCallback(boss);
  7. }
  8. }

那么现在看下如果直接把完整引用给员工:
  1. //领导
  2. public class Boss {
  3. public void viewAllSalary() {
  4. //输出所有人的工资表
  5. }
  6. public void paySalary(Employee employee, long salary) {
  7. //给某员工发放薪水
  8. }
  9. public void doThingsWithInfo(String result) {
  10. System.out.println("boss do other things according to the result:" + result);
  11. }
  12. }
  1. //员工
  2. public class Employee {
  3. public String work() {
  4. String result = "balabala";
  5. return result;
  6. }
  7. public void workAndCallback(Boss boss) {
  8. String result = work();
  9. boss.doThingsWithInfo(result);
  10. //好像还可以利用这个引用做点其他的事情
  11. //先看下其他同事的工资,哇,情敌小明的工资竟然这么高,不开心
  12. boss.viewAllSalary();
  13. //没办法,赶紧给自己多发点钱,这样可以甩小明好几条街,开心
  14. boss.paySalary(this, 999999);
  15. }
  16. }
  1. //测试类不变,老板没看出什么端倪
  2. public class Test {
  3. public static void main(String[] args) {
  4. Boss boss = new Boss();
  5. Employee employee = new Employee();
  6. employee.workAndCallback(boss);
  7. }
  8. }

2、回调的方式

  • 同步回调,即阻塞,调用方要等待对方执行完成才返回
  • 异步回调,即通过异步消息进行通知
  • 回调,即双向(类似两个齿轮的咬合),“被调用的接口”被调用时也会调用“对方的接口”

实际上我们用得最多的,还是异步回调。

2.1 同步回调

张老头准备泡茶喝,泡茶之前要先烧水。张老头把灶台点上火,把水壶放上,然后盯着水壶一直等,水开了,张老头用烧开的水,开心地泡起了茶。然后张老头喝好茶,就开始看书了。
  1. public interface Callback {
  2. void execute();
  3. }
  1. public class Elder implements Callback{
  2. private String name;
  3. public Elder(String name) {
  4. this.name = name;
  5. }
  6. public void readBook() {
  7. System.out.println(this.name + " is reading a book.");
  8. }
  9. public void drinkTea() {
  10. System.out.println(this.name + " can drink the tea right now.");
  11. }
  12. @Override
  13. public void execute() {
  14. drinkTea();
  15. }
  16. }
  1. public class Kettle {
  2. public void boilWater(final Callback callback) {
  3. System.out.println("Boiling start");
  4. int time = 0;
  5. for (int i = 0; i < 60 * 10; i++) {
  6. time += 1000;
  7. }
  8. System.out.println("Boiling the water costs " + time + "ms.");
  9. System.out.println("The water is boiling.");
  10. callback.execute();
  11. }
  12. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Elder elder = new Elder("Zhang");
  4. Kettle kettle = new Kettle();
  5. kettle.boilWater(elder);
  6. elder.readBook();
  7. }
  8. }
  9. //输出
  10. Boiling start
  11. Boiling the water costs 600000ms.
  12. The water is boiling.
  13. Zhang can drink the tea right now.
  14. Zhang is reading a book.

2.2 异步回调

还是张老头烧水喝茶的例子,他发现自己傻等着水开有点不明智,烧水可要10min呢,完全可以在这段时间先去看会儿书。等水烧开了水壶响了,再去泡茶喝,时间就利用起来了。(其他类都不变,Kettle类的boilWater方法作为线程开启,即异步
  1. public interface Callback {
  2. void execute();
  3. }
  1. public class Elder implements Callback{
  2. private String name;
  3. public Elder(String name) {
  4. this.name = name;
  5. }
  6. public void readBook() {
  7. System.out.println(this.name + " is reading a book.");
  8. }
  9. public void drinkTea() {
  10. System.out.println(this.name + " can drink the tea right now.");
  11. }
  12. @Override
  13. public void execute() {
  14. drinkTea();
  15. }
  16. }
  1. public class Kettle {
  2. public void boilWater(final Callback callback) {
  3. System.out.println("Boiling start");
  4. //开启线程,异步烧水
  5. new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. int time = 0;
  9. for (int i = 0; i < 60 * 10; i++) {
  10. time += 1000;
  11. }
  12. System.out.println("Boiling the water costs " + time + "ms.");
  13. System.out.println("The water is boiling.");
  14. callback.execute();
  15. }
  16. }).start();
  17. }
  18. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Elder elder = new Elder("Zhang");
  4. Kettle kettle = new Kettle();
  5. kettle.boilWater(elder);
  6. elder.readBook();
  7. }
  8. }
  9. //输出
  10. Boiling start
  11. Zhang is reading a book.
  12. Boiling the water costs 600000ms.
  13. The water is boiling.
  14. Zhang can drink the tea right now.

2.3 回调(双向)

“被调用的接口”被调用时也会调用“对方的接口”,这种情况就不适合我们的张老头出场了,双向回调多用于反复依赖对方的数据进行运算的时候,A系统要调用B系统的某个方法b(),但是这个b()方法中某个参数又需要A系统提供,于是需要反过来再调用A系统的某个方法a()提供参数,才能完整执行b()。

那么我为什么不在A系统运算好了参数,在调用B系统的b()方法时候直接以方法参数的形式传递呢因为你不知道b()中如何使用这个参数,或者说根据条件不同甚至不会使用到这个参数

如果这个参数的运算比较消耗资源,你不论对方使用与否都先弄出来,一股脑子塞给对方。这跟对方需要用到参数的时候,再调用你进行计算,哪个更节约资源呢?答案显而易见了。

这就跟工厂出货一样,不管市场卖不卖得掉,先生产出来,万一市场没有需求,压根没人买,这批货就烂掉了。但是如果是市场给工厂发了需求订单,工厂再进行相应生产,再出货,那效果就截然不同了。

看一个简单的例子,随机生成某个随机百分比的字符串(A实例调用了B中某个方法,而这个方法需要数据又反过来又调用了A中某个方法):
  1. public interface Callback {
  2. public double takeRandom();
  3. }
  1. public class A implements Callback{
  2. private B b = new B();
  3. @Override
  4. public double takeRandom() {
  5. System.out.println(this + " executing the method takeRandom()");
  6. return Math.random();
  7. }
  8. public void printRandomPercent() {
  9. System.out.println(this + " executing the method printRandomPercent()");
  10. System.out.println("start");
  11. //A类实例的函数调用B类实例的方法
  12. b.doPercent(this);
  13. }
  14. }
  1. public class B {
  2. public void doPercent(Callback action) {
  3. System.out.println(this + " executing the method doPercent()");
  4. double param = action.takeRandom();
  5. DecimalFormat decimalFormat = new DecimalFormat("0.00");
  6. String result = decimalFormat.format(param * 100) + "%";
  7. System.out.println("the calculate-result is " + result);
  8. }
  9. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. A a = new A();
  4. a.printRandomPercent();
  5. }
  6. }
  7. //输出
  8. callback.A@186db54 executing the method printRandomPercent()
  9. start
  10. callback.B@a97b0b executing the method doPercent()
  11. callback.A@186db54 executing the method takeRandom()
  12. the calculate-result is 7.43%

3、参考链接



[14-02] 回调

标签:接口调用   需要   领导   参数   关系   can   时间   例子   cos   

原文地址:https://www.cnblogs.com/deng-cc/p/8241894.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!