java回调总结

目录

回调简介

什么是回调函数(Callback Function)

举个栗子(回调的应用场景)

回调优点

Java的回调-浅

Java的回调-中

Java的回调-深

同步回调与异步回调


注意:本文参考  Java的回调-由浅入深(保证简单易懂)_fengye454545的博客-CSDN博客_java回调

JAVA回调机制(CallBack)详解 - 知乎

Java回调机制总结 - 带妳心菲 - 博客园

回调简介

回调是个很简单的机制。在这里我用简单的语言先来解释一下:假设有两个类,分别是A和B,在A中有一个方法a(),B中有一个方法b();

在A里面调用B中的方法b(),而方法b()中调用了方法a(),这样子就同时实现了b()和a()两个方法的功能。

疑惑:为啥这么麻烦,我直接在类A中的B.b()方法下调用a()方法就行了呗。

解答:回调更像是一个约定,就是如果我调用了b()方法,那么就必须要回调,而不需要显示调用

一句话解释回调:当前方法有一段逻辑,需要调用者来决定怎么执行。这段逻辑肯定不能写死,所以需要一个接口,来解耦当前方法和调用者!

什么是回调函数(Callback Function)

回调的应用场景非常广泛,在spring中可以看到很多应用了回调的地方,以调用相应的库函数为例子,当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。很常见的函数调用如:

a.func(Param)

但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function),这样解释估计还是比较晦涩,下面我讲用一个简单通俗的例子来解释这一术语。

举个栗子(回调的应用场景)

网上解释回调的例子有很多,大多数使用的是“算数问题”,我觉得这个解释是比较通俗易懂的,但是如果只是单单看这种场景,对于工程应用的使用场景还不是很直观,我更多的是想从实际工程应用的角度来阐述这个概念,因此如果想有个大致的概念可以参考下面这个博客的例子:

请戳这里:一个通俗有趣的回调例子(http://www.importnew.com/19301.html)

应用场景如下:

A类在内存中维护了一组词表,A类的定义如下:

定义一个泛形接口,对于返回值和参数都十分的灵活:

应用场景:

好了,上面简单的三个类就是一个回调的应用,所谓的回调函数就是process函数这个函数是被传入后又被调用的

回调优点

1、非常的灵活,用户可以在匿名内部类中定义自己的实现方法。

2、回调相当于c++中的参数里的函数指针,可以在实现了CallbackInterface接口的类中,或者匿名内部类中改变其他类中的成员变量。

3、回调还出现在button中的监听器里,安卓代码中形式如下:

4、其实定义一个新的线程然后在run方法中实现相应的逻辑也是一种回调。

5、回调的概念其实不难,难在怎么在设计中灵活的运用

Java的回调-浅

我们用例子来解释:小明和小李相约一起去吃早饭,但是小李起的有点晚要先洗漱,等小李洗漱完成后,通知小明再一起去吃饭。小明就是类A,小李就是类B。一起去吃饭这个事件就是方法a(),小李去洗漱就是方法b()。

public class XiaoMing {    
   //小明和小李一起吃饭
   public void eatFood() {
      XiaoLi xl = new XiaoLi();
      //A调用B的方法
      xl.washFace();
   }
 
   public void eat() {
      System.out.print("小明和小李一起去吃大龙虾");
   }
}

那么怎么让小李洗漱完后在通知小明一起去吃饭呢

public class XiaoMing {    
   //小明和小李一起吃饭
   public void eatFood() {
      XiaoLi xl = new XiaoLi();
      //A调用B的方法
      xl.washFace();
      eat();
   }
 
   public void eat() {
      System.out.print("小明和小李一起去吃大龙虾");
   }
}

不过上面已经说过了这个不是回调函数,所以不能这样子,正确的方式如下

public class XiaoLi{//小李
   public void washFace() {
    System.out.print("小李要洗漱");
    XiaoMing xm = new XiaoMing();
        //B调用A的方法
    xm.eat();//洗漱完后,一起去吃饭
   }
}

这样子就可以实现washFace()同时也能实现eat()。小李洗漱完后,再通知小明一起去吃饭,这就是回调。

Java的回调-中

可是细心的伙伴可能会发现,小李的代码完全写死了,这样子的场合可能适用和小明一起去吃饭,可是假如小李洗漱完不吃饭了,想和小王上网去,这样子就不适用了。其实上面是伪代码,仅仅是帮助大家理解的,真正情况下是需要利用接口来设置回调的。现在我们继续用小明和小李去吃饭的例子来讲讲接口是如何使用的。

小明和小李相约一起去吃早饭,但是小李起的有点晚要先洗漱,等小李洗漱完成后,通知小明再一起去吃饭。小明就是类A,小李就是类B。不同的是我们新建一个吃饭的接口EatRice,接口中有个抽象方法eat()。在小明中调用这个接口,并实现eat();小李声明这个接口对象,并且调用这个接口的抽象方法。这里可能有点绕口,不过没关系,看看例子就很清楚了。

EatRice接口:

public interface EatRice {
   public void eat(String food);
}

小明:

public class XiaoMing implements EatRice{//小明
    
   //小明和小李一起吃饭
   public void eatFood() {
    XiaoLi xl = new XiaoLi();
    //A调用B的方法
    xl.washFace("大龙虾", this);//this指的是小明这个类实现的EatRice接口
   }
 
   @Override
   public void eat(String food) {
    // TODO Auto-generated method stub
    System.out.println("小明和小李一起去吃" + food);
   }
}

小李:

public class XiaoLi{//小李
   public void washFace(String food,EatRice er) {
    System.out.println("小李要洗漱");
        //B调用了A的方法
    er.eat(food);
   }
}

测试Demo:

public class demo {
   public static void main(String args[]) {
    XiaoMing xm = new XiaoMing();
    xm.eatFood();
   }
}

测试结果:

 这样子就通过接口的形式实现了软编码。通过接口的形式我可以实现小李洗漱完后,和小王一起去上网。代码如下

public class XiaoWang implements EatRice{//小王
	
   //小王和小李一起去上网
   public void eatFood() {
	XiaoLi xl = new XiaoLi();
	//A调用B的方法
	xl.washFace("轻舞飞扬上网", this);
   }
 
   @Override
   public void eat(String bar) {
	// TODO Auto-generated method stub
	System.out.println("小王和小李一起去" + bar);
   }
}

Java的回调-深

上面讲的都是同步回调,可是事实上,小李要洗漱后才能吃饭,在小李洗漱的时候,小明是要做自己的事情的,比如他在玩手机,这样子就是异步回调了。而且我们把代码正规化,比如在android点击事件中,你会发现你只要实现View.setOnclickListener(this),即可实现回调,那么像这样子的规范是如何实现的,在这一节里我将会提到。废话少说,先上代码。

EatRice接口没有变化,这里就省去接口代码展示

小明:

public class XiaoMing implements EatRice{
   //小明和小李一起吃饭
   public void eatFood() {
    XiaoLi xl = new XiaoLi();
    //A调用B的方法
    xl.setEatRiceListener(this, "大龙虾");
   }
 
   @Override
   public void eat(String food) {
    // TODO Auto-generated method stub
    System.out.print("小明和小李一起去吃" + food);
   }
}

小李:

public class XiaoLi{//小李
    
   protected EatRice er;
    
   public void setEatRiceListener(EatRice er, String food) {
    this.er = er;
    washFace(food);
   }
    
   public void washFace(String food) {
        
    System.out.print("小李要洗漱");
        
    new Thread(new Runnable() {
            
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                //小李洗漱的同时小明玩手机,开启线程实现异步
                play();
                    
                Thread.sleep(10000);
                System.out.print("10秒后 ");
                //B调用A的方法
                er.eat(food);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                        e.printStackTrace();
            }
        }
    }).start();
   }
    
   //小明玩手机
   public void play() {
    System.out.println(" 小明要玩手机");
   }
}

测试结果:

 首先先打印出 "小李要洗漱 小明要玩手机",过了10秒后打印出“10秒后 小明和小李一起去吃大龙虾”。看到这里相信你对Java的回调有了新的认识吧。

同步回调与异步回调

 回调分为同步回调和异步回调, 假如以买彩票的场景来模拟, 我买彩票, 调用彩票网,给我返回的结果确定是否中奖,同步回调就是,我买了彩票之后, 需要等待彩票网给我返回的结果, 这个时候我不能做其他事情, 我必须等待这个结果, 这就叫同步回调, 同步, 就意味着等待, 我不能去做其他事情, 必须等待, 异步回调就是, 我买了彩票之后, 可以去做其他事情, 然后当彩票网有了结果和消息, 再给我返回消息, 其中最明显的方式就是在得到彩票结果的函数之中, 添加一个其他的方法, 如果我的其他方法可以立即执行, 那么就是异步的(给出是否中奖需要花费很长的时间), 而在测试函数之中, 前后两个, 那是发生在测试函数的线程之中的, 肯定是一前一后按照次序的, 在这个地方不是显示同步异步的地点.

同步回调和异步回调, 主要体现在其是否需要等待. 同步调用, 如果被调用一方的APi(第三方API), 处理问题需要花很长时间, 我们需要等待, 那就是同步回调, 如果调用完之后不需要理解得到结果, 我们调完就走, 去做其他事情, 那就是异步调用, 异步调用需要在我们调用第三方API处, 开启一个新的线程即可, 而同步调用和平常的调用没有任何区别.

OrderResult接口, 其中的方法getOrderResult

public interface OrderResult {
    /**
     * 订购货物的状态
     *
     * @param state
     * @return
     */
    //参数可以不用, 用不用按照自己的实际需求决定
    public String getOrderResult(String state);
}

Store类, 商店提供会无预定消息返回的接口, 回调OrderResult接口的方法, 给其返回预订商品的状态, 重点是returnOrderGoodsInfo(OrderResult order)方法, 体现了回调的回.

Store是被调用的一方, 被调用的一方, 要回过去调用调用一方的方法, 这个方法实际上是回调接口的方法.

public class Store {
    @Getter
    @Setter
    private String name;

    Store(String name) {
        this.name = name;
    }

    /*回调函数, 将结构传给那个我们不能直接调用的方法, 然后获取结果*/
    public String returnOrderGoodsInfo(OrderResult order) {
        String[] s = {"订购中...", "订购失败", "即将发货!", "运输途中...", "已在投递"};
        Random random = new Random();
        int temp = random.nextInt(5);
        String s1 = s[temp];
        return order.getOrderResult(s1);
    }
}

SyncBuyer类, 同步顾客类, 其中获取商品的订购状态,orderGoods(), 调用了store返回商品调用信息的returnOrderGoodsInfo()方法, 但是在Store类的returnOrderGoodsInfo()方法之中, 以OrderResult接口为参数, 反过来调用了OrderResult接口, 相当于调用了其子类SyncBuyer本身, 以他为参数, 调用了getOrderResult(String state)方法, 也就是OrderResult接口的方法, 相当于就完成了一个调用的循环, 然后取到了我们自己无法给出的结果.

这个地方的"循环", 是回调的关键所在, 需要正常调用其他外接提供方法来获取结果的一方, 继承一个回调接口, 实现它, 然后调用第三方的API方法, 第三方在我们调用的方法之中, 以回调结构为参数, 然后调用了接口中的方法, 其中可以返回相应的结果给我们, 需要说明的是, 我们虽然实现了这个接口的方法, 但是我们自己的类之中, 或者说此类本身, 却没法调用这个方法, 也可以说, 此类调用这个方法是不会产生有效的结果的. 回调的回, 就体现在此处, 在Store类之中的returnOrderGoodsInfo(OrderResult order)方法之中, 得到了很好的体现.

/*同步, 顾客在商店预订商品, 商店通知顾客预订情况*/
public class SyncBuyer implements OrderResult {
    @Getter
    @Setter
    private Store store;//商店
    @Getter
    @Setter
    private String buyerName;//购物者名
    @Getter
    @Setter
    private String goodsName;//所购商品名

    SyncBuyer(Store store, String buyerName, String goodsName) {
        this.store = store;
        this.buyerName = buyerName;
        this.goodsName = goodsName;
    }

    /*调用从商店返回订购物品的信息*/
    public String orderGoods() {
        String goodsState = store.returnOrderGoodsInfo(this);
        System.out.println(goodsState);
        myFeeling();// 测试同步还是异步, 同步需要等待, 异步无需等待
        return goodsState;

    }

    public void myFeeling() {
        String[] s = {"有点小激动", "很期待!", "希望是个好货!"};
        Random random = new Random();
        int temp = random.nextInt(3);
        System.out.println("我是" + this.getBuyerName() + ", 我现在的感觉: " + s[temp]);
    }

    /*被回调的方法, 我们自己不去调用, 这个方法给出的结果, 是其他接口或者程序给我们的, 我们自己无法产生*/
    @Override
    public String getOrderResult(String state) {
        return "在" + this.getStore().getName() + "商店订购的" + this.getGoodsName() + "玩具, 目前的预订状态是: " + state;
    }
}

Test2Callback类, 测试同步回调的结果,

public class Test2Callback {
    public static void main(String[] args) {
        Store wallMart = new Store("沙中路沃尔玛");
        SyncBuyer syncBuyer = new SyncBuyer(wallMart, "小明", "超能铁扇公主");
        System.out.println(syncBuyer.orderGoods());
    }
}

异步回调

同步回调和异步回调的代码层面的差别就是是否在我们调用第三方的API处, 为其开辟一条新的线程, 其他并无差异

例子

OrderResult接口, 其中的方法getOrderResult

public interface OrderResult {
    /**
     * 订购货物的状态
     *
     * @param state
     * @return
     */
    //参数可以不用, 用不用按照自己的实际需求决定
    public String getOrderResult(String state);
}

Store类, 商店提供会无预定消息返回的接口, 回调OrderResult接口的方法, 给其返回预订商品的状态.

public class Store {
    @Getter
    @Setter
    private String name;

    Store(String name) {
        this.name = name;
    }

    /*回调函数, 将结构传给那个我们不能直接调用的方法, 然后获取结果*/
    public String returnOrderGoodsInfo(OrderResult order) {
        String[] s = {"订购中...", "订购失败", "即将发货!", "运输途中...", "已在投递"};
        Random random = new Random();
        int temp = random.nextInt(5);
        String s1 = s[temp];
        return order.getOrderResult(s1);
    }
}

NoSyncBuyer类, 异步调用Store类的returnOrderGoodsInfo(OrderResult order)方法, 来返回商品转改的结果.

/*异步*/
@Slf4j
public class NoSyncBuyer implements OrderResult {
    @Getter
    @Setter
    private Store store;//商店
    @Getter
    @Setter
    private String buyerName;//购物者名
    @Getter
    @Setter
    private String goodsName;//所购商品名

    NoSyncBuyer(Store store, String buyerName, String goodsName) {
        this.store = store;
        this.buyerName = buyerName;
        this.goodsName = goodsName;
    }

    /*调用从商店返回订购物品的信息*/
    public String orderGoods() {
        String goodsState = "--";
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();
        System.out.println(goodsState);
        goodsState = mr.getResult();// 得到返回值
        myFeeling();// 用来测试异步是不是还是按顺序的执行
        return goodsState;
    }

    public void myFeeling() {
        String[] s = {"有点小激动", "很期待!", "希望是个好货!"};
        Random random = new Random();
        int temp = random.nextInt(3);
        System.out.println("我是" + this.getBuyerName() + ", 我现在的感觉: " + s[temp]);
    }

    /*被回调的方法, 我们自己不去调用, 这个方法给出的结果, 是其他接口或者程序给我们的, 我们自己无法产生*/
    @Override
    public String getOrderResult(String state) {
        return "在" + this.getStore().getName() + "商店订购的" + this.getGoodsName() + "玩具, 目前的预订状态是: " + state;
    }

    // 开启另一个线程, 但是没有返回值, 怎么回事
    // 调试的时候, 等待一会儿, 还是可以取到值, 但不是立即取到, 在print显示的时候, 却是null, 需要注意?
    private class MyRunnable implements Runnable {
        @Getter
        @Setter
        private String result;

        @Override
        public void run() {
            try {
                Thread.sleep(10000);
                result = store.returnOrderGoodsInfo(NoSyncBuyer.this);// 匿名函数的时候, 无法return 返回值
            } catch (InterruptedException e) {
                log.error("出大事了, 异步回调有问题了", e);
            }
        }
    }
}

Test2Callback类, 测试同步回调和异步回调的结果.

public class Test2Callback {
    public static void main(String[] args) {
        Store wallMart = new Store("沙中路沃尔玛");
        SyncBuyer syncBuyer = new SyncBuyer(wallMart, "小明", "超能铁扇公主");
        System.out.println(syncBuyer.orderGoods());


        System.out.println("\n");
        Store lawson = new Store("沙中路罗森便利店");
        NoSyncBuyer noSyncBuyer = new NoSyncBuyer(lawson, "cherry", "变形金刚");
        System.out.println(noSyncBuyer.orderGoods());
    }
}


版权声明:本文为xushiyu1996818原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。