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

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

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

  • 内部类可以有多个实例,每个实例都有自己的状态信息. 并且与外嵌类对象的信息相互独立.
  • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口.
  • 内部类是一个独立的实体,不会存在”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这个类的实例手里有它外嵌类的引用. 所以它知道它的作用域,也就是这个外嵌类实例的信息. 我的个人理解,不知道对不对.

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


图片来自维基百科.

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

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

TJI读书笔记14-闭包与回调的更多相关文章

  1. TJI读书笔记17-字符串

    TJI读书笔记17-字符串 不可变的String 重载”+”和StringBuilder toString()方法的一个坑 String上的操作 格式化输出 Formatter类 字符串操作可能是计算 ...

  2. TJI读书笔记16-异常处理

    TJI读书笔记16-异常处理 概念 基本异常情形 异常的捕获 自定义异常 异常说明 捕获所有异常 栈轨迹 重新抛出异常 Java标准异常 使用finally 异常的限制 构造器 异常的匹配 其他乱七八 ...

  3. TJI读书笔记15-持有对象

    TJI读书笔记15-持有对象 总览 类型安全和泛型 Collection接口 添加元素 List 迭代器 LinkedList 栈 Set Map Queue Collection和Iterator ...

  4. TJI读书笔记13-内部类

    TJI读书笔记13-内部类 TJI读书笔记13-内部类 创建内部类 内部类和外部类的关系 .this和.new 内部类和向上转型 局部内部类 匿名内部类 匿名内部类的定义和初始化 使用匿名内部类来实现 ...

  5. TJI读书笔记12-接口

    TJI读书笔记12-接口 抽象类和抽象方法 接口 完全解耦和策略模式 接口间的继承关系 工厂模式 乱七八糟不知道怎么归类的知识点 接口和抽象类为我们提供了更强又有力的接口和实现分离的方法. 抽象类和抽 ...

  6. TJI读书笔记10-复用类

    TJI读书笔记10-复用类 组合语法 继承语法 代理 final关键字 final的数据 final的参数 final的方法 final的类 初始化和类的加载 乱七八糟不知道怎么归类的知识点 代码复用 ...

  7. TJI读书笔记11-多态

    TJI读书笔记11-多态 再说说向上转型 多态的原理 构造器和多态 协变返回类型 使用继承进行设计 多态是数据抽象和继承之后的第三种基本特征. 一句话说,多态分离了做什么和怎么做(再次对埃大爷佩服的五 ...

  8. TJI读书笔记09-访问控制权限

    TJI读书笔记09-访问控制权限 包,package和import 权限修饰符 接口和实现 类的访问权限控制 首先问一个问题,为什么要有访问控制权限? 安全,这当然是一个很重要的原因. 让类库的使用者 ...

  9. TJI读书笔记07-初始化

    TJI读书笔记07-初始化 成员初始化 构造方法初始化 初始化块 初始化的顺序 成员初始化 java尽量去保证每个变量在使用前都会得到初始化. 对于方法局部变量,java不会自动初始化他们,如果没有显 ...

随机推荐

  1. Gridview 分多页时导出excel的解决方案

    在开发会遇到将gridview中的数据导入到excel 这样的需求,当girdview有多页数据时按照一般的方式导出的数据只可能是当前页的数据,后几页的数据还在数据库内,没有呈现到页面上,传统的方式是 ...

  2. Python使用struct处理二进制

    有的时候需要用python处理二进制数据,比如,存取文件,socket操作时.这时候,可以使用python的struct模块来完成.可以用 struct来处理c语言中的结构体. struct模块中最重 ...

  3. About SOuP

    http://www.soup-dev.com - SOuP的强大我不必多说. - Maya 也可以开发出象houdini这样有趣的节点,这再soup之前是很难想象的.所以说soup的作者Peter的 ...

  4. 如何用CSS实现在新窗口打开链接?

    *如何用CSS实现在新窗口打开链接? <style type="text/css"> <!-- .target2 a:active {test:expressio ...

  5. javascript slice

    定义和用法 slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分. 语法 stringObject.slice(start,end) 参数 描述 start 要抽取的片断的起始下 ...

  6. TensorFlow中max pooling层各参数的意义

    官方教程中没有解释pooling层各参数的意义,找了很久终于找到,在tensorflow/python/ops/gen_nn_ops.py中有写: def _max_pool(input, ksize ...

  7. shell中bc expr [ ] (( ))的使用方法

    http://blog.chinaunix.net/uid-20671208-id-3552751.html

  8. vrrp

    https://zhidao.baidu.com/question/1498182981731748379.html

  9. Java中遍历Map集合的四种方法

    在Java中如何遍历Map对象 How to Iterate Over a Map in Java 在java中遍历Map有不少的方法.我们看一下最常用的方法及其优缺点. 既然java中的所有map都 ...

  10. 解决 umount 时出现的 "Device is busy"

    1.umount, 老是提示:device is busy, 服务又不能停止的. 可以用"umount -fl"解决! 2.mount的基本用法是? 格式:mount [-参数] ...