Think In Java | 01 面向对象

Java | 面向对象

导论

对象(Object Oriented)是软件开发方法,一种编程范式。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
面向对象的思想已经涉及到软件开发的各个方面。如,面向对象的分析(OOA,Object Oriented Analysis),面向对象的设计(OOD,Object Oriented Design)、以及我们经常说的面向对象的编程实现(OOP,Object Oriented Programming)。

面向对象的常见考察点是三个基本特征:封装、继承、多态。

  • 封装(Encapsulation)
    封装的概念:也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
    操作:对象与对象之间通过消息传递机制实现互相通信(方法调用),具体的表现就是通过提供访问接口实现消息的传入传出。隐藏:通过控制访问权限来控制对象之间的互访权限,常见的访问权限:公有(public),私有(private),保护(protected)。
    封装的意义:由于封装隐藏了具体的实现,如果实现的改变或升级对于使用方而言是无感知的,提高程序的可维护性;而且封装鼓励把特定数据与对数据操作的功能打包在一起,这有利于应用程序的高内聚和去耦合。

  • 继承(Inheritance)
    继承即类之间可以继承,通过继承得到的类称为「子类」或「派生类」,被继承的类为「基类」、「父类」或「超类」。子类可以使用父类的所有功能,相对于父类更加具体化。子类具有自己特有的属性和方法,并且子类使用父类的方法也可以覆盖(重写)父类方法。
    继承的意义:继承是代码复用的基础机制,它实现了在无需重新编写原来类的情况下对这些功能进行扩展。。

  • 多态性(Polymorphism)
    多态是指,将子类类型的指针赋值给父类类型的指针,即子类型多态。不同子类对象有相同的父类接口,相同的消息给予不同的子类对象会引发不同的动作。
    实现多态,有两种方式,覆盖和重载。覆盖在运行时决定,重载是在编译时决定。
    多态的意义:提供了编程的灵活性,简化了类层次结构外部的代码,使编程更加注重关注点分离(Separation of concerns,SoC)。

抽象

对象的抽象

所有编程语言都提供抽象机制。
汇编语言是对底层机器的轻微抽象。接着出现的许多所谓“命令式”语言(如FORTRAN、BASIC、C等)都是对汇编语言的抽象。但是它们所作的主要抽象仍要求在解决问题时要基于计算机的结构,而不是基于所要解决的问题的结构来考虑。

面向对象方式通过向程序员提供表示问题空间中的元素的工具而更进了一步。这种表示方式非常通用,使得程序员不会受限于任何特定类型的问题。我们将问题空间中的元素及其在解空间中的表示称为“对象”。
所以,OOP允许根据问题来描述问题,而不是根据运行解决方案的计算机来描述问题。

Booch对对象提出了一个简洁的描述:对象具有状态、行为和标识。每一个对象都可以拥有内部数据(它们给出了该对象的状态)和方法(它们产生行为),并且都可以唯一地与其他对象区分开来。

类的抽象

世间万物皆是对象,而每一个对象都属于定义了特性和行为的某个特定的类。例如,每一只麻雀都是一个对象,但是它们都属于麻雀这个类型。
类描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类实际上就是一个数据类型。一旦类被建立,就可以根据类来创建任意个对象,然后去操作它们。
每个对象都只能满足某些请求,这些请求由对象的接口(interface)所定义,决定接口的便是类型。以电灯泡为例来做一个简单的比喻(如上图所示):
在这里插入图片描述

// 创建 Light 类型的对象 lt
Light lt = new Light();
// 使用 on()
lt.on(); 

在类型中,每一个可能的请求都有一个方法与之相关联,当向对象发送请求时,与之相关联的方法就会被调用。

面向对象的基本特征

封装

类的访问权限控制

在任何相互关系中,具有关系所涉及的各方都遵守的边界是十分重要的事情。
Java用三个关键字在类的内部设定边界,即访问指定词(access specifier):public、private、protected。

  • public修饰的元素对任何人都是可用的,private修饰的元素只有类型创建者和类型的内部方法可以访问。
  • protected关键字与private作用相当,差别仅在于继承的类可以访问protected成员,但是不能访问private成员。
  • 当元素没有任何访问指定词修饰时,归属于默认的访问权限或称之为包访问权限,在这种权限下类可以访问在同一个包(库构件)中的其他类的成员,但是在包之外,这些成员如同指定了private一样。
类的组合和聚合

新的类可以由任意数量、任意类型的其他对象以任意可以实现新的类中想要的功能的方式所组成。因为是在使用现有的类合成新的类,所以这种概念被称为组合(composition),如果组合是动态发生的,那么它通常被称为聚合(aggregation)。组合经常被视为“has-a”(拥有)关系,就像我们常说的“汽车拥有引擎”一样。
在这里插入图片描述

头部 人类 住址 Composition Aggregation

继承

继承的基本形式

在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。
泛化或叫继承(generic)可以达到此效果:以现有的类为基础,复制它,然后通过添加和修改这个副本来创建新的类。需要注意的是,当源类(被称为基类、超类或父类)发生变动时,被修改的“副本”(被称为导出类、继承类或子类)也会反映出这些变动。
在这里插入图片描述
继承使用基类型和导出类型的概念表示了这种类型之间的相似性。一个基类型包含其所有导出类型所共享的特性和行为。

例如:
基类是几何形,具有尺寸、颜色、位置等通用特性,具有绘制、擦除、移动和着色等通用行为。
在此基础上,可以继承出具体的几何形状——圆形、正方形、三角形等。
在这里插入图片描述
但是,每一种都具有额外的特性和行为,例如某些形状可以被翻转。
在这里插入图片描述
同时,某些相同的行为,其具体的行为内容可能并不相同,例如几何形状都可以计算面积,但是计算的具体公式是不同的。
使导出类和基类之间产生差异的方法是改变现有基类的方法的行为,这被称之为覆盖(overriding)那个方法。
在这里插入图片描述

单根继承结构

在Java中(事实上还包括除C++以外的所有OOP语言),所有的类最终都继承自单一的基类,这个终极基类的名字就是Object。
在单根继承结构中的所有对象都具有一个共用接口,所以它们归根到底都是相同的基本类型,都具备某些功能。
由于所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵局,单根继承结构使垃圾回收器的实现变得容易得多。

多态

在处理类型的层次结构时,经常想把一个对象不当作它所属的特定类型来对待,而是将其当作其基类的对象来对待。
这样处理,当从基类中又泛化出新的类型后,原有使用父类的代码并不会受到影响,这使得人们可以编写出不依赖于特定类型的代码。这种能力可以极大地改善我们的设计,增加的代码的可扩展性,同时也降低软件维护的代价。
这样也带来了一个问题,当使用基类的某个方法时,我们无法是执行哪个子类的代码。
为了解决这个问题,面向对象程序设计语言使用了后期绑定的概念,即程序直到运行时才能够确定代码的地址。Java使用一小段特殊的代码来替代绝对地址调用,这段代码使用在对象中存储的信息来计算方法体的地址,这就是Java的动态绑定。
例如,定义了一个可以操作图形(Shape)基类的方法,可以进行绘画和擦除:

public void doSomething(Shape shape){
	shape.draw();
	// ...
	shape.erase();
}

在使用 doSomething() 方法的地方可以这样写:

Circle circle = new Circle();
Line line = new Line();
doSomething(circle );
doSomething(line );

把将导出类看做是它的基类的过程称为向上转型(upcasting)。

对象的创建和存储

在使用对象时,最关键的问题之一便是它们的生成和销毁方式。每个对象为了生存都需要资源,尤其是内存。当我们不再需要一个对象时,它必须被清理掉,使其占有的资源可以被释放和重用。

Java采用了动态内存分配方式。使用new关键字时,就会在堆(heap)的内存池中动态地创建对象。这样的方式,在运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。

因为存储空间是在运行时被动态管理的,所以需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间。在堆栈中创建存储空间和释放存储空间通常各需要一条汇编指令即可,分别对应将栈顶指针向下移动和将栈顶指针向上移动。创建堆存储空间的时间依赖于存储机制的设计。

Java提供了被称为“垃圾回收器”的机制,它可以自动发现对象何时不再被使用,并继而销毁它。垃圾回收器“知道”对象何时不再被使用,并自动释放对象占用的内存。这一点同所有对象都是继承自单根基类Object以及只能以一种方式创建对象(在堆上创建)这两个特性结合起来,使得用Java编程的过程较之用C++编程要简单得多,所要做出的决策和要克服的障碍也要少得多。

有五个不同的地方可以存储数据:
1)寄存器。
这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象(另一方面,C和C++允许您向编译器建议寄存器的分配方式)。
2)堆栈。
位于通用RAM(随机访问存储器)中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些Java数据存储于堆栈中——特别是对象引用,但是Java对象并不存储于其中。
3)堆。
一种通用的内存池(也位于RAM区),用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当需要一个对象时,只需用new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代价:用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间(如果确实可以在Java中像在C++中一样在栈中创建对象)。
4)常量存储。
常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分隔离开,所以在这种情况下,可以选择将其存放在ROM(只读存储器)中[1]。
5)非RAM存储。
如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是流对象和持久化对象。在流对象中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象”中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他媒介上的事物,在需要时,可恢复成常规的、基于RAM的对象。Java提供了对轻量级持久化的支持,而诸如JDBC和Hibernate这样的机制提供了更加复杂的对在数据库中存储和读取对象信息的支持。

基本数据类型

在这里插入图片描述
所有数值类型都有正负号,boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true或false。

基本类型具有的包装器类,使得可以在堆中创建一个非基本对象,用来表示对应的基本类型。
例如:

char c = 'x';
Character ch = new Character(c);

或:

Character ch = new Character('x');

或使用自动包装功能将自动地将基本类型转换为包装器类型:

Character ch = 'x';

也可以反向转换:

Char c = ch;

高精度数字
Java提供了两个用于高精度计算的类:BigInteger和BigDecimal。虽然它们大体上属于“包装器类”的范畴,但二者都没有对应的基本类型。
BigInteger支持任意精度的整数。也就是说,在运算中,可以准确地表示任何大小的整数值,而不会丢失任何信息。
BigDecimal支持任何精度的定点数,例如,可以用它进行精确的货币计算。
只不过必须以方法调用方式取代运算符方式来实现。由于这么做复杂了许多,所以运算速度会比较慢。在这里,我们以速度换取了精度。

容器

在Java中,具有满足不同需要的各种类型的容器,例如List(用于存储序列),Map(也被称为关联数组,用来建立对象之间的关联),Set(每种对象类型只持有一个),以及诸如队列、树、堆栈等更多的构件。

容器的特点:
一,不同容器提供了不同类型的接口和外部行为。堆栈相比于队列就具备不同的接口和行为,也不同于集合和列表的接口和行为。它们之中的某种容器提供的解决方案可能比其他容器要灵活得多。
二,不同的容器对于某些操作具有不同的效率。

最好的例子就是两种List的比较:ArrayList和LinkedList。它们都是具有相同接口和外部行为的简单的序列,但是它们对某些操作所花费的代价却有天壤之别。在ArrayList中,随机访问元素是一个花费固定时间的操作;但是,对LinkedList来说,随机选取元素需要在列表中移动,这种代价是高昂的,访问越靠近表尾的元素,花费的时间越长。而另一方面,如果想在序列中间插入一个元素,LinkedList的开销却比ArrayList要小。上述操作以及其他操作的效率,依序列底层结构的不同而存在很大的差异。我们可以在一开始使用LinkedList构建程序,而在优化系统性能时改用ArrayList。接口List所带来的抽象,把在容器之间进行转换时对代码产生的影响降到最小限度。

泛型

使用容器时,只需在其中置入对象引用,稍后还可以将它们取回即可。但是由于容器只存储Object,所以当将对象引用置入容器时,它必须被向上转型为Object,因此它会丢失其身份。当把它取回时,就获取了一个对Object对象的引用,而不是对置入时的那个类型的对象的引用。

所以,怎样才能将它变回先前置入容器中时的具有实用接口的对象呢?这里再度用到了转型,但这一次不是向继承结构的上层转型为一个更泛化的类型,而是向下转型为更具体的类型。这种转型的方式称为向下转型。

Java SE5的重大变化之一就是增加了参数化类型,在Java中它称为范型。
参数化类型机制:创建一个知道自己所保存的对象的类型的容器,从而不需要向下转型以及消除犯错误的可能。

为了利用范型的优点,很多标准类库构件都已经进行了修改:

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

异常处理

Java的异常处理在众多的编程语言中格外引人注目,因为Java一开始就内置了异常处理,而且强制你必须使用它。
值得注意的是,异常处理不是面向对象的特征——尽管在面向对象语言中异常常被表示成为一个对象。异常处理在面向对象语言出现之前就已经存在了。

并发编程

在计算机编程中有一个基本概念,就是在同一时刻处理多个任务的思想。许多程序设计问题都要求,程序能够停下正在做的工作,转而处理某个其他问题,然后再返回主进程。基于此类要求,我们把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称之为线程,上述概念被称为“并发”。
如果操作系统支持多处理器,那么每个任务都可以被指派给不同的处理器,并且它们是在真正地多线程并行执行。但是,如果有多个并行任务都要访问同一项资源,那么就会出问题。因此,过程被设计为:某个任务锁定某项资源,完成其任务,然后释放资源锁,使其他任务可以使用这项资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值