前言:
我们之所以将自然界分解,组织成各种概念,并按其含义分类,主要是因为我们是整个口语交流社会共同遵守的协定的参与者。这个协定以语言的形式固定下来……除非赞成这个协定中规定的有关语言信息的组织和分类,否则我们根本无法交谈。
——Benjamin Lee Whorf(1897~1941)
第一章 对象导论
1.1抽象过程
一、编程语言
人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。
- 汇编语言是对底层机器的轻微抽象。
- 接着出现的许多所谓“命令式”语言(如FORTRAN、BASIC、C等)都是对汇编语言的抽象
这些语言所作的主要抽象仍要求在解决问题时要基于计算机的结果,而不是基于所要解决的问题的结构来考虑。
- 面向对象方式通过向程序员提供表示问题空间中的元素的工具而更进一步。
我们将问题中的元素及其在解空间的表示称为“对象”。程序可以通过添加新类型的对象使自身适用于某个特定空间。每个对象看起来都有点像一台微型计算机——它具有状态,还具有操作,用户可以要求对象执行这些操作。
所以,OOP(面向对象编程)允许根据问题来描述问题,而不是根据运行解决方案的计算机来描述问题。
二、面向对象语言的总结
Alan Key曾作出如下面向对象语言的总结:
- 万物皆为对象。将对象视为奇特的变量,它可以存储数据,除此之外,你还可以要求它在自身上执行操作。
- 程序是对象的集合,它们通过发送消息来告知彼此所要做的。
- 每个对象都有自己的由其他对象所构成的存储。
- 每个对象都拥有其类型。
- 某一特定类型的所有对象都可以接收同样的消息。
Booch 则提出了一个更加简洁的描述:对象具有状态、行为和标识。每一个对象在内存中都有一个唯一的地址。
1.2每个对象都有一个接口
关键字class的由来: 在程序执行期间具有不同的状态而其他方面都相似的对象会被分组到对象的类中
一、对象
- 每个类的成员或元素都具有某种共性。
- 每个成员都有其自身的状态
- 实体就是对象,每一个对象都属于定义了特性和行为的某个特定的类。
那么因为类描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类实际上就是一个数据类型。
例如所有浮点型数字具有相同的特性和行为集合。
所以说,创建一个新的类,可以说就是创建一个新的数据类型,去解决具体的问题。
根据需求,添加新的数据类型来扩展编程语言。
例子:
Light lt = new Lignt();
lt.on()
- Light :类型名称
- lt: 特定Light对象名称
- on():接口确定了读某一特定对象所能发出的请求。(这里的接口也就是类的所有方法)
- new :通过new 方法来创建该类型的新对象
1.3每个对象都提供服务
程序本身将向用户提供服务,它将通过调用其他对象提供的服务来实现这一目的。
将对象看做是服务提供者还有一个附带的好处:有助于提高对象的内聚性。
高内聚是软件设计的基本质量要求之一。
在良好的面向对象设计中,每个对象那个都可以很好地完成一项任务,但是它并不试图做更多的事。
1.4被隐藏的具体实现
访问控制的存在原因:
- 让客户端程序员无法触及他们不应该触及的部分
- 允许库设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员。
Java用三个关键字在类的内部设定边界:public private protected.
- public : 紧随其后的元素对任何人都是可用的。
- private : 除类型创建者和类型的内部方法之外的任何人都不能访问的元素。
- protected : 与private 作用相当,差别在于继承的类可以访问protected成员,但是不能访问private成员。
1.5复用具体实现
代码复用是面向对象程序设计院所提供的最了不起的优点之一。
组合:使用现有的类合成新的类。
如果组合是动态发生的,那么它通常被称为聚合。
在建立新类时,应该首先考虑组合,因为它更加简单灵活。
1.6继承
Q:如何做到以现有的类为基础,复制它,然后通过添加和修改这个副本来创建新累。
A:通过继承。不过也有例外,当源类(或被称为基类,超类,父类)发生变动时,被修改的"副本"(子类,继承类)也会反映出这些变动
一个基类型包含其所有导出类型所共享的特性和行为。
当继承仙有泪时,也就创造了新的类型。这个新的类型不仅包括现有类型的所有成员(注意:基类的private成员其实也被继承过来,只是子类无法使用,不可访问),而且更重要的是它复制了基类的接口。
如何使基类与导出类产生差异:
- 直接在导出类中添加新方法。
- 覆盖基类的方法(重写基类的方法)。
public class A {
private String data1 = "private";
public String data2 = "public";
}
public class B extends A{
public void show(){
System.out.println(data1);//这里报错,其实已经把A的data1继承过来,只是不能访问
System.out.println(data2);
}
}
可以通过:
- “是一个” 来判断继承关系,比如:一个圆形就是一个几何形状
- “像是一个” 关系判断子类新的方法:新类型有旧类型的接口,但是它还包含其他方法,所以不能说他们完全相同。比如,热力泵(既可以加热,又可以制冷)像是一个制冷系统。
1.7伴随多态的可互换对象
public class Shape {
public void draw(){
System.out.println("Shape drawing...");
}
}
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Circle drawing...");
}
}
public class Main {
public static void main(String[] args){
Shape shape = new Shape();
use(shape);
Circle circle = new Circle();
use(circle);
}
public static void use(Shape shape){
shape.draw();
}
}
输出结果为:
Shape drawing…
Circle drawing…
可以看到use(Shape shape) 这个方法需要传送一个Shape对象的类型,而传入继承自Shape的Circle可以成功,并且调用的是Circle的方法,而不是Shape的方法。
这是如何发生的呢?
一个非面向对象编程语言的编译器产生的函数调用会引起所谓的前期绑定。
这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。
为了解决这个问题,面向对象程序设计院使用了后期绑定的概念。
当向对象发送消息时,被调用的代码直到运行时才能确定。在Java中,动态绑定是默认行为,不需要添加额外的关键字来实现多态。
回到这个代码中来:
将导出类(Circle)当做它的基类的过程称为向上转型。注意这里并不是说“如果是Circle,请这样做,如果是Shape,请那样做…”,这里所表达的意思仅仅是"无论你是Circle还是其他的,我知道你是一个Shape,那么久可以draw()自己,那么你就去draw()吧,但是要注意细节的正确性。"
1.8单根继承结构
Java中所有的类都有一个终极基类Object。这样做有很多好处.
-
单根继承结构保证所有对象都具备某些功能。
-
单根继承结构使垃圾回收期的实现变得容易很多,不会因无法确定对象的类型而陷入僵局。
1.9容器
对于有些问题,你不可能知道具体需要多少个对象去解决?那么就需要容器,在任何需要时都可以扩充自己以容纳你置于其中的所有东西。
这种新的对象类型持有对其他对象的引用。
好的OOP语言都有一组容器,Java在其标准库中也包含有大量的容器。
- List:用于存储序列
- Map:也被称为关联数组,用来建立对象之间的关联
- Set:每种对象类型只持有一个
- …以及队列、数、堆栈等
不同种类的容器有以下好处:
- 不同容器提供了不同类型的接口和外部行为,容器的解决方案将更为灵活。
- 不同容器对于某些操作具有不同的效率。
注意:
Java容器只能存储Object类,那么其他所有类存入,都会被向上转型为Object类。因此它会丢失身份。
1.9.1从而有了泛型来实现正确的Object向下转型
泛型,记住这些对象究竟是什么类型,从而实现正确的向下转型。同样,泛型的声明使得容器只能接受相应的类型存入容器。
public class Shape {
public String data = "1";
public void draw(){
System.out.println("Shape drawing..."+data);
}
}
public class Main {
public static void main(String[] args){
Shape shape = new Shape();
shape.draw();//这里输出结果是 Shape drawing...1
shape.data="2";
Object a = (Object)shape;
shape = (Shape)a;
shape.draw(); //这里输出结果是 Shape drawing...2
}
}
以上代码表明,一个类被向上转型为Object,只要能正确的向下转型回来,数据并不会丢失。所以可以类比容器的对类的转型。
1.10对象的创建和生命期
像C++这样的语言中,必须通过编程方式来确定何时销毁对象,这可能会因为不能正确处理而导致内存泄漏。Java提供了称为“垃圾回收器”的机制,它可以自动发现对象何时不再被使用,并继而销毁它,处理内存释放问题。
1.11异常处理:处理错误
异常是一种对象,它从出错点被“抛出。并被专门设计用来处理特定类型错误的相应的异常处理器”捕获“。异常不能被忽略,所以它保证一定会在某处得到处理。
1.12并发编程
在计算机编程中有一个基本概念,就是在同一时刻处理多个任务的思想。通常,线程只是一种为单一处理器分配执行时间的手段。但是如果操作系统支持多处理器,那么每个任务都可以被指派给不同的处理器,并且它们是在真正地并行执行。
并发有一个隐患:共享资源。
可以共享的资源必须在使用期间被锁定。
Java的并发是内置于语言之中的,JavaSE5已经增添了大量额外的库支持。