陆离的光怪世界

Thinking-in-Java

    BUG制造者     黄金屋·Java

  1. 对象介绍
    1. 抽象过程
    2. 代码重用——组合和继承
    3. 多态——运行时绑定
    4. 单继承
    5. 容器集合
    6. 泛型——参数化类型
    7. 对象创建&生存时间
    8. 异常处理
    9. 并发编程
    10. Java和互联网
      1. C/S模型
      2. 客户端编程
      3. 服务端编程
  2. 并发
    1. 优势
      1. 执行更快
      2. 改善代码设计
    2. 简单的线程
    3. 使用Executors

Thinking in Java的读书笔记,记下来作为自己的参考

目录

本书的目录如上。

对象介绍

我们将自然界的东西拆分、识别并分类,把它们组织成概念,并赋予其意义(有点机器学习的感觉),这一切都是因为我们(作为人类)共同遵守一个协议——即语言,通过语言来描述一切我们所知道的事物,并互相交流。

上面这段话说明了几个问题:

  • 语言是表达用的,正如编程语言(如Java)也是用来表达的——首先将待解决的问题表达(描述),然后将解决方案用编程语言表达出来。正如乔布斯说的,Computers are bicycles for the mind
  • 我们能够对话(或者语言能够被理解)的前提是,我们共同遵守一个协议,或者说语言有一定的规则,就像任何编程语言都有特定的语法规则一样。

抽象过程

程序员所做的事情,其实就是在问题空间解空间找到一个合适的映射。将现实世界中的问题抽象为程序语言设计的发展过程,经历了很多变化。LISP认为所有的问题都是lists(就像to do list,可以被一件一件勾选掉的那种);APL认为所有问题都是有规则的(algorithmic);Prolog认为所有的问题都是一系列的决定(decisions)。这些语言可以解决特定领域的问题,但是都有比较大的局限性。相对来说,像Java一样的面向对象程序设计语言就更具有普遍意义(general),可以将几乎任何类型的问题转化为程序设计。

SmallTalk,作为第一个成功的面向对象语言,也是Java设计时的参照语言,具有五大特点(据Alan Kay说):

  1. 万物皆对象
  2. 程序就是一堆对象互相通信,告诉对方该做什么
  3. 对象中可以包含其他对象——以实现复杂对象的构建
  4. 每个对象都有一个类型(因为每个object一定属于某个class,而class可以说是类型(type)的程序术语。
  5. 同一类型的所有对象能接收相同的消息

用一句更简洁的话来说,

一个对象拥有状态、行为和身份

状态即其包含的字段(property);行为即其方法(methods);身份即在内存(或存储)中的地址。

代码重用——组合和继承

组合(composition),即一个对象中包含其他的对象(如果是动态的composition,也可以叫做aggregation,区分不大);继承,即子类继承父类,扩展父类功能。

多态——运行时绑定

你也许定义了一个父类和很多子类,但是在程序中调用方法时还不确定需要调用哪一个子类的方法,需要根据运行时的状态决定。这时候,多态的好处就体现出来了——你可以在使用时用父类的对象调用子类对应的方法,然后让编译器在运行时决定使用的是哪个类的方法,并进行类型检查等。

C++中也有类似的功能(晚绑定/运行时绑定),不过需要显式声明——即声明为虚函数。C++中默认是以早绑定(即编译时绑定)来实现的,而Java中的默认实现方式就是运行时绑定,方便了开发人员并使程序更易扩展。

举个例子,Shape基类和其各种形状的子类:

1
2
3
4
5
6
Circle circle = new Circle();
Triangle triangle = new Triangle();
Line line= new Line();
doSomething(circle);
doSomething(triangle);
doSomething(line);

这个运行时绑定的行为也叫upcasting(向上投掷),实际上cast可以理解为映射,即根据调用时的子类型,判断它是哪个父类的子类。

单继承

C++相对于Java可以多继承,有一点考虑是为了向后兼容C,单继承相对于多继承有几个好处(多数是方便做一些公共的操作):

  • 所有对象有个基础的父类:Object,可以对所有类进行一些基础的操作如toString
  • 垃圾回收——回收垃圾时不会存在**无法确定要回收的垃圾是什么类型的情况**
  • 一些系统级的操作,如异常处理。

容器集合

为了方便运行时动态确定所要创建对象的数量,Java提供了容器集合工具(类似C++的STL)。如List、Map、Set类,还有队列、树、栈等等。之所以有这么多种基本的容器类,是因为不同的类有不同的功能,且有不同的执行效率。如ArrayListLinkedList,前者擅长随机访问元素,后者擅长插入删除。

泛型——参数化类型

泛型的存在就是为了方便检查类型,避免向下映射时出错。如,

1
List<Shape> shapes = new ArrayList<Shape> ();	

对象创建&生存时间

对比C++,Java有garbage collector可以自动判断对象何时该被销毁。尽管在栈上创建对象可以节省资源(控制对象回收时间),但是对于一些普遍的问题,无法确定其需要的存储空间以及生存时间(在运行时才能确定),使用Java在堆上来创建对象(new)并让垃圾收集器来处理对象回收效率会更高。

异常处理

异常处理相当于程序执行时的另一条路线,或者说小路。这使得异常部分的代码与正常执行的代码不会冲突。被抛出的异常程序中出错时的返回值是不同的。 被抛出的异常是一定要被处理的,并且异常处理提供了一个可以从异常情况恢复的方法。不只是退出程序,你还可以使程序恢复执行,增加程序的鲁棒性。

Java中如果没有正确处理异常,将会在编译时报错,因此你不得不在写代码时就检查可能会出现的错误,而不是到运行时异常退出。

并发编程

并发编程的一个应用就是用户界面(user interface)。通过使用子任务(多线程),用户按了按钮会立即响应而不是要等到程序完成了当前的任务再响应。并且,如果有硬件支持(多处理器),多个线程不仅能并发,还能并行。

Java和互联网

C/S模型

客户端编程

服务端编程

并发

并发(多线程)之所以这么重要是因为你可以在很多地方看到它,甚至使用它。如在Java web开发中,最基本的库Servlet就被设计为多线程的,因为web服务器往往拥有多核处理器,使用多线程能够最大限度地利用硬件的能力(就算没有多处理器,想想web应用使用单线程来处理是多么可怕)。再如GUI编程,用户的交互体验可不能被单线程的程序一直等待给毁了!(类似JavaScript中的异步处理)。不只Java支持多线程,很多语言都支持。只不过Java中并发编程使用的较广泛而已。

优势

执行更快

对于多处理器而言,多线程编程显然能更快(如,一个任务放在一个处理器执行);对于单处理器而言,多线程也能使程序更快,且程序中遇到一些外部事件(如I/O)而阻塞时更能体现出多线程的优势(只阻塞某线程,其它线程正常运行)。

如果没有多线程,想象这样一个场景(在单处理器上进行事件驱动型编程):

在一个GUI程序中,有一个“退出”按钮。要实现响应式设计(用户按了后立马退出),你就必须在自己的代码中的每个任务块中加上一个判断(是否检测到退出按钮被按到),不然用户按了按钮也要等到当前任务执行完才能退出。如果使用多线程的方式,单独开一个线程检测用户输入,尽管这个线程可能多数时候会被阻塞,但是程序保证了及时响应。

实现并发的另一种方式就是从操作系统层面来做,使用进程。每个进程拥有独立的地址空间,多任务操作系统可以一次运行多个进程,通过算法将CPU分配给每个进程使用,看起来好像每个进程都有自己的CPU。使用多进程的好处是操作系统将进程隔离开了,每个进程有独立的资源。而使用多线程时则要考虑资源的管理,如对互斥资源加锁等。

一些编程语言把并发的任务完全隔离开来,如Erlang。而Java则是把多个任务分成一个进程中的多个线程,且此操作对于操作系统是透明的(即操作系统也不知道你的程序在捣鼓些什么,开了多少个线程)。

改善代码设计

协作多线程(cooperative multithreading)。

简单的线程

定义任务 —— 放入线程 —— 创建多个线程

使用Executors

页阅读量:  ・  站访问量:  ・  站访客数: