zoukankan      html  css  js  c++  java
  • 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这个类的实例手里有它外嵌类的引用. 所以它知道它的作用域,也就是这个外嵌类实例的信息. 我的个人理解,不知道对不对.

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

    .1474386153799
    图片来自维基百科.

    比如酒店的叫醒服务. 酒店(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类里还是一个匿名内部类里不重要. 画了一个聊胜于无的图…我觉得我貌似看懂了…

    image

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

  • 相关阅读:
    基于概率论的分类方法:朴素贝叶斯
    【目录】机器学习 笔记
    决策树原理及分类实战
    k-近邻算法实现“电影、约会网站、手写数字识别”分类
    多线程互斥锁、读写锁
    21.快速排序实现(快排)
    系统架构资料收集
    20.排序之冒泡、直插、希尔排序方法
    19.散列表(哈希表)查找
    18.平衡二叉树(AVL树)、多路树查找(B树)概念
  • 原文地址:https://www.cnblogs.com/thecatcher/p/5891164.html
Copyright © 2011-2022 走看看