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

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

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

  • 内部类可以有多个实例,每个实例都有自己的状态信息. 并且与外嵌类对象的信息相互独立.
  • 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口.
  • 内部类是一个独立的实体,不会存在”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. 实现windows批处理下的计时功能

    有时在执行完一段windows的批处理后,想知道这个过程花费了多少时间,如果是windows下的c代码可以在过程前后分别调用GetTickCount(),然后相减即可得到花费的时间. 但是如果在批处理 ...

  2. 基于服务(Web Service)的文件管理Winform客户端实现(二)

    上一篇主要介绍文件管理服务有哪些方法,实现了哪些功能. 基于服务(Web Service)的文件管理Winform客户端实现(一) 本篇将根据文件服务服务实现Winform客户端设计.开发. 3.   ...

  3. OAF_文件系列8_实现OAF处理Excel的JXL包详解

    写入/读取Excle,设置打印样式排版,锁定列与行,设置打印标题,给Excle加密. http://www.blogjava.net/xing5156/archive/2011/11/18/36422 ...

  4. IOC框架整体介绍

    1.Castle Windsor 2.Autofac 3.Unity 4.Spring.NET 5.StructureMap 6.Ninject

  5. JS移动端如何监听软键盘回车事件

    移动端经常项目中会有搜索之类的功能,一般实现的是按搜索按钮进行搜索,如果要像PC端一样实现按回车键进行搜索该怎么实现呢? 方法很简单,就是在搜索框的input外面套一个form标签  注意点:form ...

  6. lucene 内存索引 和文件索引 合并

    IndexWriter.addIndexes(ramDirectory); http://blog.csdn.net/qq_28042463/article/details/51538283 在luc ...

  7. maven添加仓库地址

    mvn install时,好多包从mvn中央库下载不下来,搜索到一个maven库地址:http://conjars.org/repo/,将其添加到maven仓库中 编辑 conf/setting.xm ...

  8. Convert Excel data to MDB file

    所需组件: microsoft ado ext. 2.8 for ddl and security 或者更新的组件. 添加: using ADOX;using System.Runtime.Inter ...

  9. 调用 SSPI 失败,请参见内部异常。接收到的消息异常,或格式不正确。

    完整异常信息: System.Security.Authentication.AuthenticationException: 调用 SSPI 失败,请参见内部异常. ---> System.C ...

  10. OPNET下 op_pk_copy()的时间问题

    op_pk_copy()是生成新的数据包,包的op_pk_create_time_get()是新数据包的生成时间,统计数据包的端到端时延,以及服务时延需要注意: