板凳-----------On_Java第1章 什么是对象

面向对象程序设计(Object-oriented Programming,OOP)的概念和背景。

1.1抽象过程

人们所能够解决的问题的复杂性直接取决于抽象的类型和质量.
汇编: 是对底层机器的轻微抽象。“命令式”语言是对 汇编语言的抽象
面向对象编程则更进一步 它为程序员提供了一些能够呈现问题空间元素的工具。这种呈现方式具备足够的通用性 使得程序员不再局限于特定的问题。而这些问题空间中的元素及其解决方案空间中的具体呈现 我们称其为“对象”要注意的是 有些对象并不支持问题空间的类比 。其背后的理念则是 通过添加各种新的对象 程序可以将自己改编为一种描述问题的语言。

5个基本特征 代表了纯粹的面向对象编程的方式。
1.万物皆对象。
2.一段程序实际上就是多个对象通过发送消息来通知彼此要干什么。
3. 从内存角度而言 每一个对象都是由其他更为基础的对象组成的。
4. 每一个对象都有类型。
5. 同一类型的对象可以接收相同的消息。

程序员必须建立
1机器模型 位于“解空间”内, 这是你对问题建模的地方
2实际待解决问题的模型 位于”问题空间”内, 问题存在的地方
两者之间的关联.

1.2每个对象都有一个接口

创建抽象数据类型 “ 类” 是面向对象编程的一个基本概念。抽象数据类型的工作原理和内置类型几乎一样 你可以创建某种类型的变量 在面向对象领域 这些变量叫“对象 ”“实例” 随后你就可以操作这些变量 叫作“发 消息” “发送请求”
即你发送指令给対象 然后対象自行决定怎么处理 。
所谓对象,就是一个类的实例。
对象具有 状态(内部数据给出对象的状态)、行为(方法产生行为)和标识(每个对象在内存中有唯一的地址,区别于其他对象,是标识)。
类就是有相同特性(数据元素)和行为(功能)的对象集合。
这里给一个例子:
类型名称 Light
接口 on()、off() 、brighten()、dim()
接口确定了对某个对象可以发出的请求(可以看到这里所谓的接口,就是类中的成员函数)。
Light lt = new Light();
lt.on();
上面代码中,我们首先创建了一个 Light 对象:定义这个对象的引用(lt),然后调用 new 方法来创建一个该类型的新对象。为了向对象发送消息,需要声明对象的名称(这里是lt),并以原点连接一个消息请求。
可以说就是在类中的成员函数叫接口,在对象中的成员函数叫请求。

1.3 每个对象都提供服务

讲对象看做是其可以提供服务的集合,使得程序具有更好的可读性和可重写性。
比如 :
1、一个对象包含了所有可能的打印布局,通过查找它可以知道如何打印一张支票。
2、另一个或一组对象则作为通用打印接口,负责连接所有不同型号的打印机,不负责记账。 也许你需要购买该功能而非自行创建 。
3、还有一个对象负责整合前两个对象提供的服务以完成打印任务。
因此, 每一个对象都提供了一种配套服务。在面向对象领域 出色的设计往往意味着一个对象只做好一件事---- 绝不贪多。这条原则不只适用于那些从外部购买的对象。 比如打印接口对象 ,也适用于那些可复用的对象 (比如支票排版对象)。

1.4 被隐藏的具体实现(访问控制)

将程序员分为
一是“ 类的创建者” 负责创建新数据类型的人 ,
二是“客户程序员” 在自己的应用程序里使用现有数据类型的人。
客户端程序员可以基于类创建者创建好的类 来做一些快速开发。
创建者,不希望类内所有的内容都可以被访问,而需要隐藏一部分(通常是内部脆弱的部分),这样可以减少bug。
访问控制,就是做这件事的。这里有三个关键字:
public:谁都可以访问
private:类创建者和类内部 方法可以访问
protected :与private差别在于,继承类可以访问
如果没有指定关键字,java 中默认访问权限为 **包package访问权限,即同一个包总可以访问。
设置访问控制的
首要原因就是防止客户程序员接触到他们本不该触碰的内容,即那些用于数据类型内部运转的代码 而非那些用于解决特定问题的接口部分。
第二个原因则是 让库的设计者在改变类的内部工作机制时 不用担心影响到使用该类的客户程序员。

1.5 复用具体实现

代码复用是对象程序语言的一个巨大优点。
这里所谓的复用,就是可以用之前的写好的类。有两种方法:组合、继承。
所谓组合就是创建一个新的类去调用已有类,新类就可以叫做一个组合。
public class People{
// People 方法
}
class Student{
People people = new People();
// Student 方法
}
而继承,则是用 extends 关键字继承父类所有的方法。
public class People{
// People 方法
}
class Student extends People{
// 新写的Student 方法。
已经包含People 父类所有非private方法。
}
什么时候用哪个呢?其实很简单,如果 存在 “is-a”关系,(比如 Bee 是一个 Insect),即所有的Insect方法都需要,用继承。如果是 “has-a”关系(Bee 有一个 Insect的 move功能),就要用组合。
组合和继承的区别

1.6 继承

基本概念你已经清楚了,常见的做法是:把比较核心的概念写在基类中,继承类表示这些核心的各种实现方方式。
这里需要明白一个概念,基类和导出类具有相同的类型。
继承已有的类将产生新类。这个新的子类不但会继承其基类的所有成员。 虽然private员是隐蔵且不可访问的, 而且更重要的是: 子类也会继承基类的接口。也就是说,有基类对象能够接收的消息,子类对象也一样能够接收。我们可以通过一个类所接收的消息来确定其类型。 所以从这一点来说, 子类和基类拥有相同的类型。
既然基类和子类拥有相同的基础接口 就必然存在接口的具体实现。这意味着, 当一个对象接收到特定的消息时, 就会执行对应的代码。如果你继承了一个类并且不做任何修改的话 这个基类的方法就会原封不动地被子类所继承。

如何让继承类和基类产生不同,这里给出了两种途径:添加新方法、方法重写(overriding)。第一种就加一个方法、第二种直接给出已有方法的重新定义(实现代码)。
第一种方法非常简单直接: 为子类添加新的方法。因为这些方法并非来自基类。 所以背后的逻辑可能是, 基类的行为和你的预期不符。 于是你添加了新的方法以满足自己的需求。有时候 继承的这种基础用法能够完美地解决你面临的问题。不过, 你需要慎重考虑是否基类也需要这些新的方法。
还有一个替代方案是考虑使用“组合” 。
有时候继承意味着需要为子类添加新的方法[Java尤其如此 其用于继承的关键字就是“扩展 ” extends)] 但这不是必需的。还有一种让新类产生差异化的方法更为重要,即修改基类已有方法的行为。 我们称之为“重写”该方法。
这里在两种方法的选择上(是否需要增加新的方法),给出了 “is-a” & “is-like-a” 的关系,很直观。

1.7 伴随多态的可互换对象

来自继承机制的一种重要技巧, 编译器并非通过传统方式来调用方法。对于非面向对象编译器而言 其生成的函数调用会触发“前期绑定n ( early binding )。前期绑定意味着编译器会生成对一个具体方法名的调用。 该方法名决定了被执行代码的绝对地址。但是对于继承而言程序直到运行时才能明确代码的地址, 所以就需要引入其他可行的方案以确保消息可以利发送至対象。
面向对象的语言使用了 动态绑定(后期绑定)的概念。当向对象发送消息时,被调用的代码直到运行时才可以确定。
发送消息后,编译器只确保被调用方法的存在,并对调用参数和返回值类型检查(无法提供此类保证的语言叫 若类型语言),但是不知道将被执行的确切代码。
C++使用virtual关键字来达到此目的。在这些编程语言中 方法并不默认具备动态绑定特性。Java 具备动态绑定特性
将导出类看做基类的过程叫做向上转型(upcasting)。
// 首先写一个方法,可以看到这个方法的参数是 Shape 对象
void doSomething(Shape shape){
shpae.erase();
shape.draw();
}
// 方法的具体实现.
// 这里写的参数是 circle对象,但是可以通过 upcasting,Circle对象可以被看做 Shape对象。
doSometihing(circle);
doSometihing(triangle);

1.8 单根继承结构

在 java(事实上包括 c++在内的所有OOJ语言)中,所有的类都继承自一个基类。 这个终极基类叫做 Object。这样的单根继承结构有以下优点。
所有对象都具有共同的接口, 因此它们都属于同一个终极基类(upcasting思想)。 来自C++ 则无法确保所有对象都属于同一个基类。从向后兼容的角度来看 这种限制性较小的设计方式对C语言更为友好。必须自己手动构建类的层次, 这样才能拥有其他面向对象编程语言默认提供的便捷性。
单根继承结构保证所有对象都具备某些功能(Object的功能)
单根继承结构使得垃圾回收器的实现变得容易很多,而垃圾回收器正是 java 相对 C++ 的重要改进之一。由于所有对象都保证具有基类信息,因此 不会因为无法确定对象类型而陷入僵尸。

1.9 容器

如果事先不知道解决一个问题需要多少个对象,就不知道需要创建多大的空间来存储对象(比如一个数组创建时,不知道里面的元素有多少个)。这里我们就要用到容器。
一个容器的对象,只需要扩充自己来容纳其中的东西,所以我们并不需要知道到底有多少个对象置于容器中。
java 中,提供了不同种类的容器,比如 List(用于存储序列)、Map(用来建立对象之间的关系)、Set(不含重复的对象,或者说元素)、以及队列、树、堆栈等很多组件。
不同的容器提供了不同的接口和行为,
不同容器对于某些操作具有不同的效率。
参数化类型
在 Java SE5之前,所有容器存储的对象都只具有Java中的通用类型:Object。 这里就有一个问题,不同对象存储时候 upcasting,但是具体用的的时候需要 donwcasting,这样就需要编写额外的代码来解决 downcasting 的问题。

如果知道的话, 我们就不再需要向下转型。 也避免了在此期间可能出现的报错。这个问题的解决方案就是“参数化类型M( parameterized type ) 机制。一个被参数化的类型是一种特殊的类, 可以让编译器自动适配特定的类型。

通过在一对尖括号中间加上类名来定义泛型
ArrayList shapes = new ArrayListo();

1.10 对象的创建和生命期

C++语言的宗旨:是效率优先。 所以它交给程序员来选择。如果要最大化运行时效率, 可以通过栈区( 也叫“ 自动变量”或“局部变虽”) 保存对象,或者将对象保存在静态存储区里。 这样在编写程序时就可以明确地知道对象的内存分配和生命周期。这种做法会优先考虑分配和释放内存的速度。 有些情况下是极为有利的。但是,代价就是牺牲了灵活性。 因为你必须在编写代码时就明确对象的数量、生命周期以及类型。
在内存池里动态创建对象, 这个内存池叫作“堆” ( heap )。如果使用这个方案, 直到运行时你才能知道需要多少对象 以及它们的生命周期和确切的类型是什么。也就是说 这些信息要等到程序运行时才能确定。如果你需要创建一个新对象可以直接通过堆来创建。因为堆是在运行时动态管理内存的, 所以堆分配内存所花费的时间通常会比栈多一些(不过也不一定) 。栈通常利用汇编指令向下或向上移动栈指针( stackpointer) 来管理内存。 而堆何时分配内存则取决于内存机制的实现方式。
动态创建对象的方案基于一个普遍接受的逻辑假设 即对象往往是复杂的。所以在创建对象时, 查找和释放内存空间所带来的额外开销不会造成严重的影响。因此更大的灵活性才是解决常规编程问题的关键。
对象的生命周期。对于那些允许在栈上创建对象的编程语言, 编译器会判断对象将会存在多久以及负责自动销毁该对象。java采用动态内存分配方式,创建一个对象时,使用 new 关键字构建此对象的动态实例,存储在 堆heap 中。同时,java中 垃圾回收器被设计用来处理内存释放问题。在堆上创建的对象,编译器对其生命周期一无所知,而垃圾回收器可以自动发现对象何时不被使用,从而销毁对象,释放内存。

1.11 异常处理:处理错误

异常处理则是将编程语言甚至是操作系统和错误处理机制直接捆绑在一起。异常是从错误发生之处“抛 出”的对象。 而根据错误类型 它可以被对应的异常处理程序所“捕获 ”。而每当代码出现错误时, 似乎异常处理机制会使用一条特殊的、并行的执行路径来处理这些错误。这是因为它确实采取了一条单独的运行路径, 所以不影响正常执行的代码。同时,这一点也降低了你编写代码的成本。 因为你不用经常反复检查各种错误了。此外, 抛出的异常也不同于方法返回的错误值或者方法设置的错误标识。 因为这两者是可以被忽略的。但是异常不允许被忽略。 所以这就确保了异常一定会在必要的时候被处理。最后 异常为 我们提供了一种可以让程序从糟糕的情况中恢复过来的方法。即便发生了意外, 我们也还是有机会修正问题以及让程序重新恢复运行。 而不是只能结束程序了事, 而这一点无疑会增强许多程序的稳健性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值