TJI读书笔记14-闭包与回调

时间:2023-03-09 01:50:20
TJI读书笔记14-闭包与回调

为什么要使用内部类?内部类继承自某个类或者实现某个接口,内部类的代码可以操作外嵌类的对象. 这不是使用内部类的理由. 那么为什么使用内部类呢? 我觉得如果使用其他办法可以更好的解决需求问题,那为什么要使用那么复杂的内部类呢?

内部类的好处之一,可以提供更强的封装性. 像前面一篇中的实例,很多时候,我们甚至都不需要知道内部类的具体类型就可以使用它了. 但是这个理由说服力度不够,更重要的是,内部类提供了一种更合理的多重继承的解决方案. 因为每个内部类都可以独立的继承一个实现.

埃大爷说,除了解决多重继承的问题之外,内部类还有一些优良的特征:

  • 内部类可以有多个实例,每个实例都有自己的状态信息. 并且与外嵌类对象的信息相互独立.
  • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口.
  • 内部类是一个独立的实体,不会存在”is-a”的关系
  • 内部类的对象创建的时刻 并不依赖于外嵌类创建的时刻(The point of creation of the inner-class object is not tied to the creation of the outer-class object. 这个是啥意思?)

闭包与回调

真是智商捉急,花了两天时间才略微搞明白什么是闭包什么是回调. 很神奇的是,搜了很多文档,最后竟然是在知乎上看到一个别人的回答之后豁然开朗. 发现知乎真是个神奇的地方…本节的很多内容借鉴了知乎用户futeng的回答,原文地址:https://www.zhihu.com/question/19801131/answer/26586203

闭包是一个可调用的对象,它包含了创建它的作用域的信息. 按照埃大爷的说法,闭包其实就是一个由函数和其引用环境组合成的一个实体. 先看埃大爷的代码:

1.interface Incrementable{
2. void increment();
3.}
4.
5.class Callee1 implements Incrementable{
6.
7. private int i = 0;
8. public void increment(){
9. i++;
10. System.out.println(i);
11. }
12.}
13.
14.class MyIncrement{
15. public void increment() {
16. System.out.println("Other Operation");
17. }
18. static void f(MyIncrement mi){
19. mi.increment();
20. }
21.}
22.
23.class Callee2 extends MyIncrement{
24.
25. private int i =0;
26. public void increment() {
27. super.increment();
28. i++;
29. System.out.println(i);
30. }
31.
32. private class Closure implements Incrementable{
33. public void increment() {
34. Callee2.this.increment();
35. System.out.println(Callee2.this);
36. }
37. }
38. Incrementable getCallBackReference(){
39. return new Closure();
40. }
41.}
42.
43.class Caller{
44. private Incrementable callbackReference;
45. public Caller(Incrementable cbh) {
46. callbackReference = cbh;
47. }
48. void go(){
49. callbackReference.increment();
50. }
51.}
52.
53.
54.public class Callbacks {
55. public static void main(String[] args) {
56. Callee1 c1 = new Callee1();
57. Callee2 c2 = new Callee2();
58. System.out.println(c2);
59. MyIncrement.f(c2);
60.
61. Caller caller1 = new Caller(c1);
62. Caller caller2 = new Caller(c2.getCallBackReference());
63.
64. caller1.go();
65. caller1.go();
66. caller2.go();
67. caller2.go();
68. }
69.}

32-37行这样一个内部类其实就是一个闭包,它知道所创建它的作用域的信息. 简单来说,就是Closure这个类的实例手里有它外嵌类的引用. 所以它知道它的作用域,也就是这个外嵌类实例的信息. 我的个人理解,不知道对不对.

关于回调
当一个方法调用另外一个实例的方法的时候,被调用的这个方法的执行依赖于调用者的某个方法. 被调用的方法会去调用调用者的某个方法. 被调用方法调用的调用者的这个方法就是回调方法. (跟绕口令似的)

TJI读书笔记14-闭包与回调
图片来自*.

比如酒店的叫醒服务. 酒店(Hotel)提供一个叫醒服务方法叫wakeUp(). 但是它允许你自己定义被叫醒的方式beWaked().你可以在beWaked()中定义自己被叫醒的方式,是敲门,打电话还是要求服务员踹开门把你从床上拎起来. 使用的时候,把beWaked()方法传入wakeUp(). 到了时间,酒店就可以调用你的beWaked方法来把你叫醒.

1.public class Guest {
2.
3. public void beWaked() {
4. System.out.println("call me via phone");
5. }
6. public static void main(String[] args) {
7. Guest guest = new Guest();
8. Hotel hotel = new Hotel();
9.
10. hotel.wake(guest);
11. }
12.}
13.
14.public class Hotel {
15. public void wake(Guest guest){
16. guest.beWaked();
17. }
18.}

这是一个比较简单的例子.

感觉知乎答友futeng的例子更好. 直接上终极版:

1.public interface DoHomework {
2. void doHomeWork(String question,String answer);
3.}
4.//========================================================================
5.public class Student implements DoHomework{
6. public void doHomeWork(String homework,String answer) {
7. System.out.println("作业本");
8. if("1+1=?".equals(homework)){
9. System.out.println("作业: "+homework+"答案: "+answer);
10. }else {
11. System.out.println("作业: "+homework+"答案: 不知道~");
12. }
13. }
14.
15. public static void main(String[] args) {
16. Student student = new Student();
17. String aHomework = "1+1=?";
18. RoomMate roomMate = new RoomMate();
19. roomMate.getAnswer(aHomework, student);
20.
21. }
22.}
23.//========================================================================
24.public class RoomMate {
25.
26. public void getAnswer(String homework, DoHomework someone) {
27. if("1+1=?".equals(homework)) {
28. someone.doHomeWork(homework, "2333333");
29. } else {
30. someone.doHomeWork(homework, "(空白)");
31. }
32. }
33.
34.}

看这个例子,其实跟上面的意思差不离. 学霸好室友提供代写作业服务getAnswer(). 只需要将作业题目和自己的引用传递给他,他就会帮你写作业. 但是这里需要注意的是,实际传入的是一个接口. 这里真是豁然开朗,之前对向上转型一直是心存疑虑的,这里提供了一个很好的使用向上转型的场景. 比如这里其实是可以直接传入student实例的引用,但是这样很不好. 我只是想让你帮我写作业. 但是我把整个引用都给你了,等于把自己所有接口都暴露出去了. 那样对student而言岂不是很不安全?
所以这里可以让student实现一个DoHomework的function接口,作为有代写作业职业操守的学霸好室友,我只要求”传入”这个接口. 这样学霸好室友就只能看到doHomeWork这一个方法. 同时也提供了更强的扩展. 那其他实现了DoHomework接口的人也可以找我写作业了.

1.public class RoomMate{
2. public void getAnswer(String homework,DoHomework someone) {
3. if("1+1=?".equals(homework)) {
4. someone.doHomeWork(homework, "2333333");
5. } else {
6. someone.doHomeWork(homework, "(空白)");
7. }
8. }
9. public static void main(String[] args) {
10. RoomMate roomMate = new RoomMate();
11. roomMate.getAnswer("1+1=?", new DoHomework() {
12.
13. @Override
14. public void doHomeWork(String question, String answer) {
15. System.out.println("问题"+question+" 答案: "+answer);
16. }
17. });
18. }
19.}

还有这个匿名内部类,这也是一种形式的回调. 理解起来不难,回调的时候,只要找到主调函数所需要的那个函数就可以了. 具体它是被定义在student类里还是一个匿名内部类里不重要. 画了一个聊胜于无的图…我觉得我貌似看懂了…

TJI读书笔记14-闭包与回调

再回头看埃大爷的代码,Caller类中定义的go()方法其实就用到了回调.