Java编程思想读书笔记一:对象导论

抽象过程

1. 万物皆为对象

理论上讲,我们可以抽取一个待解决问题中的任何概念化构件(狗、建筑、服务)等,将其表示为程序中的对象。

2. 程序是对象的集合

对象之间是通过发送消息来告知彼此所要做的。通俗来说,一个程序是一些对象的集合体,程序之间的通信实际是对象与对象之间的通信,要想进行通信,就必须要发送一条消息。更确切的说,我们可以把消息理解为是对对象的某个特定方法的请求。

3. 每个对象都有自己的由其他对象所构成的存储

换句话说就是一个对象可能由其他多个对象共同创建。

4. 每个对象都拥有其类型

类型是用来区分不同对象的特性,也就是可以发送什么样的消息给它。

5. 某一特定类型的所有对象都可以接收同样的消息

这里是说对象之间信息交互的关系。如果一个“圆形”对象也是一个“几何形”对象,那么这个“圆形“对象一定也能接收发给”几何形”对象的消息。

每个对象都有一个接口

对象之间传递消息时,消息的接收是通过接口来传递的,接口对应着对象内部的一个方法,接口暴露给外部对象,内部方法决定消息的执行方式。

每个对象都提供服务

可以把对象理解为服务提供者,每一个对象都有它所对应的服务需要提供,因此我们在创建一个对象或者说是构建一个类的时候,要思考能否更大程度的抽象出一个对象,以解决一个实际的问题。

被隐藏的具体实现

我们将开发人员分为两种:

  1. 类创建者
    类创建者负责构造类,包括类的属性和接口、方法,这种类只向客户端程序员暴露必要的部分,而隐藏其他部分,防止客户端程序员对类中重要的数据进行修改以对程序造成重要损失,我们称作是访问控制。
  2. 客户端程序员。

访问控制

客户端程序员负责快速准确的编写客户端程序。访问控制的第一个原因是让客户端程序员无法接触他们不该接触的部分,因为有些内容对程序本身来说很重要但是与应用程序业务没有关系。
另一个原因是便于类创建者在不影响客户端程序的前提下来修改类内部的私有内容。
Java中访问控制有三个级别:publicprivateprotected

  • public顾明思议是公有的,表示由public修饰的内容可以供所有人可见。
  • private为私有的,表示由private修饰的部分只有自己可见。
  • protectedprivate类似,差别在于继承类中,子类可以访问父类中的protected修饰区而无法访问private修饰区。此外还有默认的访问权限,即没有上述三种修饰的情况下,默认在包内可见,在包外等同于private级别。

复用具体实现(组合)–has-a的关系

一个类并不能完全的被复用到另外一种场景,这时我们需要创建一个新的类,并引入之前的类的对象,引入的对象我们声明为private级别的成员对象。一个新建的类可以有很多个其它类型的对象,这种方式我们称为组合
它可以有效的实现我们想要的方式的组合,组合是has-a的关系,表明某某类有a对象的属性。同时,由于组合类的成员变量通常都被修饰为private,所以客户端程序员并不能访问他们,同时也方便了类创建者在不影响程序功能的前提下修改这些成员变量。

继承– is-a的关系

当我们创建一个类时,如果另一个新类与这个类功能相似,我们仍然需要创建这个新类。解决这个问题的办法就是继承。继承虽然也是一个新类,但是这个类是由基类(俗称父类)衍生的导出类(俗称子类)。

子类与父类之间的关系

  1. 子类拥有父类所有非private的对象、属性、接口,此外可以根据不同的需要增加不同的功能。父类可以有很多个子类,它包含了子类的所有公共部分。如“几何形”是父类,每一个几何形都具有尺寸、颜色、位置等,同时每一个几何形都可以被绘制、擦除和移动。在此基础上可以导出它的子类“三角形”、“平行四边形”等,他们拥有父类的属性、方法之外还有自己独特的属性,例如有的形状可以被翻转等。

  2. 子类不光继承了父类的属性,同时也继承了父类的方法,也就是说所有发给父类的消息,都可以发给子类。
    子类对接口的实现方法可以不改变,即访问子类的接口实际是与访问父类相同,当然也可以自己“覆盖”父类接口的方法,也就是说我和父类使用相同的接口,但是我们做不同的事情(is-a的关系)。同时如前边所说,子类也可以自己新增方法来满足自己的需求。(is-like-a的关系,在子类中增加了新的方法,所以这种相同的关系并不完全)

伴随多态的可互换对象

前期绑定和后期绑定

子类继承父类,同时继承了父类的方法,也就是说发给父类的消息同时能够发给子类。那么当我们把子类对象看成泛化的基类对象时,如果有个方法是让泛化的父类操作自己,那么编译器在编译时不知道该执行哪段代码的。一个非面向对象程序的函数调用是前期绑定,函数要执行的代码在程序运行之前就已经确定了,然而在OOP中,直到程序执行我们才知道哪段代码被执行了,所以为了解决消息执行哪段代码的问题,Java使用了后期绑定的概念,使用一小段特殊的代码来代替非面向对象中所说的绝对地址调用,这段代码使用在对象中存储的信息来计算方法的地址

后期绑定的案例

1
2
3
4
5
6
7
8
9
10
11
void doSomething(Shape shape){
shape.erase();
//...
shape.draw();

// 这个方法可以与任意`Shape`类型的对象交互,如果程序中有它的子类调用了该方法

Circle circle = new Circle();
Triangle triangle = new Triangle();
cicle.doSomething();
triangle.doSomething();

编译对doSomething的调用会自动处理,而不必考虑是什么类型。这里编译器将子类看成父类,这个过程称作向上转型。这段代码并不是说“如果是Cirle就xxx,如果是Triangle就xxx”,而是“你就是个Shape,只管做就好,注意细节问题的正确性”。其中细节问题由编译器进行处理。

单根继承结构

在Java语言中,所有的类都是Object类的子类,拥有着Object类的基本方法。这种单根继承结构有很大的好处,比如在垃圾回收中可以避免由于不知道对象的类型而无处下手,因为他们都可以使用Object类的方法,并且所有对象都可以很容易的在堆上创建。

容器

有时候我们并不知道处理一个问题需要多少个对象,或者他们需要存活多久,那么我们就不知道该怎么样存储这些对象。Java语言中创建了一种对象类型,叫做容器(也叫集合)。这种新的对象类型内部有对其他对象的引用,当你需要的时候你可以很容易的扩充容器的空间来容纳对象。Java中提供了多种容器类型,如List(用于存储序列)、Map(也被称为关联数组,用来建立对象的关联)、Set(每种对象类型只持有一个)。因为容器中存储了其他对象的引用,所以根据单根继承结构,容器中的对象类型都是Obejct类型,这样很方便其他对象类型进行转型。但是从Object类型转到其它特定类型(称作向下转型)很容易发生错误,所以Java SE5之后引入了泛型的概念,参数传递使用一对<>,<>内部可以传递任意类型的对象,这便解决了向下转型带来的安全隐患。

对象的创建和生命周期

对象的创建方式有两种:

  1. 在编程时由程序员控制,将对象置于栈中或者是静态区域,这种的好处是知道对象的大小、生命周期。同时也限制了对象的灵活性。
  2. 通过new在内存堆中动态的创建对象,这种方式的好处是灵活,需要的时候直接在内存中创建即可,实现了存储空间的动态管理。

Java中完全采用了动态内存的分配方式。

对象的生命周期

对于生命周期,

  • 上的生命周期,编译器可以知道它什么时候销毁,并自动回收。
  • 而在上创建的对象,必须由程序员自己指定回收时间,如果没有指定则会造成内存泄漏。Java中提供了垃圾回收机制,可以自动的回收在上创建的对象。

异常处理:处理错误

Java内置了异常处理机制,相当于一条与正确执行并行的路线,当程序发生异常时会执行异常的代码。同时,允许程序在异常中进行处理并返回到正确的结果中去

并发编程

Java与其他语言一样,提供多线程的并发编程方式,提高程序运行效率。

Java与Internet

Java不仅可以编写客户端程序,还可以编写网络Web应用程序。