在知乎上看到一篇回帖,深刻简出的描述了回调函数的概念及其用法,以为很好,转帖过来,安全
做者:futeng 连接:http://www.zhihu.com/question/19801131/answer/26586203 来源:知乎 前言ide
在Java社区的各类开源工具中,回调方法的使用俯拾便是。因此熟悉回调方法无疑能加速本身对开源轮子的掌握。 网上搜了一些文章,奈何对回调方法的介绍大多只停留在什么是回调方法的程度上。本篇文章尝试从回调方法怎么来的、为何要使用回调方法以及在实际项目中如何使用等方面来介绍下。函数
场景工具
场景选择的得当与否,很影响读者的继续阅读的兴趣甚至理解的主动性(长期做为互联网技术博文读者的我,深有感触)。 好场景私觉得是:熟悉且简单。spa
本例当心翼翼选择的场景是:写做业。(hope you like)对象
本身写继承
注:写做业这个动做至少交代三个方面:谁,什么动做(写),写什么。 下面先从(有个学生,写,做业)开始。接口
# 1. 有个学生 Student student = new Student(); 游戏
# 2. 该学生有写做业这个动做须要执行 student.doHomeWork(someHomeWork); get
# 3. 注意到这个写做业这个动做是须要获得入参“做业”的后才能进行的。因此给这个学生new了个简单的题目作。
String aHomeWork = "1+1=?";
student.doHomeWork(aHomeWork);
至此,完成写做业的动做。
完整代码
public class Student {
public void doHomeWork(String homeWork) {
System.out.println("做业本");
if("1+1=?".equals(homeWork)) {
System.out.println("做业:"+homeWork+" 答案:"+"2");
} else {
System.out.println("做业:"+homeWork+" 答案:"+"不知道~~");
}
}
public static void main(String[] args) {
Student student = new Student();
String aHomeWork = "1+1=?";
student.doHomeWork(aHomeWork);
}
}
程序执行
做业本 做业:1+1=? 答案:2
咱们必定要把焦点聚焦到,”写做业“这个需求上面。
该学生写做业的方法是现成的,可是须要有做业做为入参,怎么获取做业才是完成动做的关键。但愿这点能深深印入咱们的脑海。
让室友帮忙解答上面的例子中该同窗本身调用本身的方法,把收到的homework直接写了。
可是现实可能会出现各类各样的问题致使该同窗不能(xiang)本身来作。好比他想玩游戏或者有约会。因此他拜托了
他的好室友(roommate)来帮忙写下。该怎么实现呢。
#1. 由于室友帮忙写,因此在doHomeWork动做里面,就不须要有逻辑判断的代码,由于舍友会直接把答案写进来。改为: student.doHomeWork(aHomeWork, theAnswer);
#上句中作做业的动做支持传入“做业”和“答案”,有了这两个,就说明能作好做业了。
#其中aHomeWork做业是已知的,可是theAnswer这个答案倒是室友提供的。
#室友怎么才能提供答案呢,最简单是,室友这个对象直接提供一个传入做业,传出答案的方法,这样该同窗就能够直接调用了。
RoomMate roomMate = new RoomMate();
String theAnswer = roomMate.getAnswer(aHomeWork);
student.doHomeWork(aHomeWork, theAnswer);
完整代码
public class Student {
public void doHomeWork(String homeWork, String answer) {
System.out.println("做业本");
if(answer != null) {
System.out.println("做业:"+homeWork+" 答案:"+ answer);
} else {
System.out.println("做业:"+homeWork+" 答案:"+ "(空白)");
}
}
public static void main(String[] args) {
Student student = new Student();
String aHomeWork = "1+1=?";
RoomMate roomMate = new RoomMate();
String theAnswer = roomMate.getAnswer(aHomeWork);
student.doHomeWork(aHomeWork, theAnswer);
}
}
public class RoomMate {
public String getAnswer(String homework) {
if("1+1=?".equals(homework)) {
return "2";
} else {
return null;
}
}
}
程序执行
做业本 做业:1+1=? 答案:2
怒,说好的回调方法呢~~
由于到目前为止,不须要使用回调方法。
技术老是伴随新的需求出现的。
好,给你新的需求。
注意重点来了咱们回顾下这两行代码
#室友写好做业
String theAnswer = roomMate.getAnswer(aHomeWork);
#该同窗直接抄答案,完成做业
student.doHomeWork(aHomeWork, theAnswer);
该同窗想了想,你给了答案有屁用,还得要我本身誊写到做业本上面去(执行本身的作做业方法)。你就不能直接调用个人作做业方法帮我把答案写好,把做业作完得了。
让室友直接把做业写了经不住该同窗的软磨硬泡,“中国好室友”答应了。怎么实现呢。
再回顾下作做业的全过程
#待解决的做业
String aHomeWork = "1+1=?";
#室友写出答案
String theAnswer = roomMate.getAnswer(aHomeWork);
#该同窗调用,本身把答案写到做业本。(也便是这个步骤不给调用了)
student.doHomeWork(aHomeWork, theAnswer);
#作做业必须得调用这个方法,而根据需求这个方法必须由室友去调用。那很显然,该室友得保持一个该同窗的引用,才能正常调用啊。
#灯灯灯~
#室友说,那你在调用getAnswer方法的时候,除了传入做业,还须要把本身的引用放里面。这样我作完了,直接调用你的作做业方法就好了。
roomMate.getAnswer(aHomeWork,student);
完整代码
public class Student {
public void doHomeWork(String homeWork, String answer) {
System.out.println("做业本");
if(answer != null) {
System.out.println("做业:"+homeWork+" 答案:"+ answer);
} else {
System.out.println("做业:"+homeWork+" 答案:"+ "(空白)");
}
}
public static void main(String[] args) {
Student student = new Student();
String aHomeWork = "1+1=?";
RoomMate roomMate = new RoomMate();
roomMate.getAnswer(aHomeWork,student);
}
}
public class RoomMate {
public void getAnswer(String homework, Student student) {
if("1+1=?".equals(homework)) {
student.doHomeWork(homework, "2");
} else {
student.doHomeWork(homework, "(空白)");
}
}
}
执行程序
做业本 做业:1+1=? 答案:2
回调方法 在上述“让室友直接把做业写了”的例子中,其实已经体现了回调的意思。 场景的核心在于这位学生要把做业给作了。 简单点描述:这位学生告诉室友要作什么做业,并把本身的引用也给了室友。该室友获得做业,作完后直接引用该学生并调用其作做业的方法,完成代写做业的任务。 稍微复杂点描述:该学生作做业的方法有两个入参,一个是做业题目(已知),一个是做业答案(未知)。室友为了帮助他写做业提供了一个方法,该方法有两个入参,一个是做业题目,一个是该学生的引用(解出答案得知道往哪写)。程序执行时,该学生只要调用室友的代写做业方法就好了。一旦室友获得答案,由于有该学生的引用,因此直接找到对应方法,帮助其完成做业。 再复杂点描述:学生调用室友的替写做业方法,注册了题目和本身的引用。室友的替写做业方法被调用,则会根据题目完成做业后,再回调该同窗写做业方法,完成做业。 再抽象点描述:类A调用类B的方法b(传入相关信息),类B的方法在执行完后,会将结果写到(再回调)类A的方法a,完成动做。(其实方法a就是传说中的回调方法啦) 最抽象的描述:调用,回调。
接口方式的回调方法 经常使用回调方法的同窗可能会说,我历来也没见过直接把对象的引用写到第一次调用方法里面的。 嗯,是的,下面就来填上述例子留下的“天坑”(实际项目中常见到)。 问题:在调用方法中直接传对象引用进去有什么很差? 只说一点,只是让别人代写个方法,犯得上把本身所有暴露给别人吗。万一这个别人是竞争对手的接口咋办。这就是传说中的后面代码吗(/tx)。 总之这样作是很是不安全的。 所以,最常规的《调用,回调》实现,是(你已经猜到了)使用接口做为引用(说的不严谨)传入调用的方法里面。 我认可,怎么将思路跳转到使用接口的花了我好长时间。 咱们再看RoomMate类的getAnswer方法。
public class RoomMate {
public void getAnswer(String homework, Student student) {
if("1+1=?".equals(homework)) {
student.doHomeWork(homework, "2");
} else {
student.doHomeWork(homework, "(空白)");
}
}
}
关键在于,该方法的用途是来解决某学生提出的某个问题。答案是经过学生的doHomeWork方法回调传回去的。那假设有个工人也有问题,这位室友该怎么解决呢。再开个方法,专门接收工人的引用做为传参?固然不用,只要你这个引用包含了doHomeWork()方法,那么不论你是工人、警察仍是环卫工人,直接调用getAnswer()方法就能解决你提的问题。 至此咱们的思路达到了:全部的对象要有同一个方法,因此自热而然就引出了接口概念。只要这些对象都实现了某个接口就好了,这个接口的做用,仅仅是用来规定那个作做业的方法长什么样。这样工人实现了该接口,那么就有了默认继承的作做业方法。工人再把本身的引用抛给该室友的时候,这个室友就不须要改动任何代码,直接接触答案,完成任务了。 建立一个作做业的接口,专门规定,须要哪些东西(问题和答案)就能作做业.
public interface DoHomeWork{
void doHomeWork(String question,String answer);
}
改动下中国好室友的解答方法。任意一个实现了DoHomeWork 接口的someone,都拥有doHomeWork(String question,String answer)的方法。这个方法就是上面已经提到的“回调方法”。 someone先调用下好室友的getAnswer()方法,把问题和本身传进来(此为调用),好室友把问题解答出以后,调用默认提供的方法,写完做业。 思考下,由于是以接口做为参数类型的约定,在普通对象upcast向上转型以后将只暴露接口描述的那个方法,别人获取到这个引用,也只能使用这个(回调)方法。至此,遗留的重大安全隐患重要解决。 完整代码
public class RoomMate {
public void getAnswer(String homework, DoHomeWork someone) {
if("1+1=?".equals(homework)) {
someone.doHomeWork(homework, "2");
} else {
someone.doHomeWork(homework, "(空白)");
}
}
}
package org.futeng.designpattern.callback.test1;
public class Worker implements DoHomeWork {
@Override
public void doHomeWork(String question, String answer) {
System.out.println("做业本");
if(answer != null) {
System.out.println("做业:"+question+" 答案:"+ answer);
} else {
System.out.println("做业:"+question+" 答案:"+ "(空白)");
}
}
public static void main(String[] args) {
Worker worker = new Worker();
String question = "1+1=?";
new RoomMate().getAnswer(question, worker);
}
}
执行程序
做业本 做业:1+1=? 答案:2
至此,调用+回调的文章是否是写完了呢。 咳咳,还木有。你们喝点茶再忍耐下。(我都写一天了 - -) 常规使用之匿名内部类做为平凡的屁民,实用主义是咱们坚持的生存法则。 因此凡事用不到的技术均可以不学,凡事学了却不用的技术等于白学。 咱们以前已经定性,中国好室友RoomMate类拥有接受任何人任何问题挑战的潜质。 自从好室友出名以后,有个不知道什么工做(类型)的人也来问问题。反正只要实现了回调接口,好室友都能调用你默认继承的回调方法,那就放马过来吧。
package org.futeng.designpattern.callback.test1;
public class RoomMate {
public void getAnswer(String homework, DoHomeWork someone) {
if("1+1=?".equals(homework)) {
someone.doHomeWork(homework, "2");
} else {
someone.doHomeWork(homework, "(空白)");
}
}
public static void main(String[] args) {
RoomMate roomMate = new RoomMate();
roomMate.getAnswer("1+1=?", new DoHomeWork() {
@Override
public void doHomeWork(String question, String answer) {
System.out.println("问题:"+question+" 答案:"+answer);
}
});
}
}
看到稍显奇怪的roomMate.getAnswer("1+1=?", new DoHomeWork() {的哪一行,其实这里new的是DoHomeWork接口的一个匿名内部类。这里我想你们应该本身动脑想一想,调用+反调,这个过程是怎么实现的了。 至因而否使用匿名内部类是根据具体使用场景决定的。普通类不够直接,匿名内部类的语法彷佛也不够友好。 开源工具中对回调方法的使用上述匿名内部类的示例才是开源工具中常见到的使用方式。