计算机是头脑延伸的工具,是一种不一样类型的表达媒体。本文以背景性的和补充性的材料,介绍包括开发方法概述在内的面向对象程序设计(Object-oriented Programming,OOP)的基本概念。
本文经过概念+代码的方式,来帮助读者了解面向对象程序设计的全貌。java
机器模型:位于解空间
内,是对问题建模的地方;能够这样理解,汇编语言和命令式语言,在解决问题时要基于计算机的架构;所以架构限定了解决方案,因此说机器模型是解空间。程序员
实际待解决问题:问题空间
,是问题存在的地方编程
抽象的类型和质量,决定了人们所可以解决的问题的复杂性。抽象的类型指的是“所抽象的是什么”。一种是在机器模型和实际待解决问题的模型之间创建联系的抽象;另外一种是只针对待解决问题建模。而面向对象则是向程序员提供表示问题空间中元素的工具,咱们将问题空间中的元素及其在解空间中的表示称为“对象”。微信
什么是对象?对象具备状态、行为和标识。每个对象均可以拥有内部数据和方法,而且能够惟一的与其余对象区分开来
对象:具备状态、行为和标识的实体。如银行存款帐户是一个类,那么具体的每一个人的银行存款帐户就是这个类目下的对象。多线程
类:能够看做类型来考虑。好比说鸟类,是动物中的其中一种类型。架构
全部的对象都是惟一的,但同时具备相同的特性和行为的对象也都归属于某个特定的类。
类在Java中用关键词class表示。每一个类的对象都具备某种共性和个性,如银行存款帐户,每一个帐户中都有余额的属性,但每一个帐户中的余额又不一样。在实际中,面向对象程序设计语言都用class关键字来表示数据类型,换而言之,每个类都是一个数据类型。程序员能够自由地添加新的类(数据类型)来扩展编程语言,对实际问题进行处理。并发
面向对象的挑战之一,就是在问题空间的元素和解空间的对象之间建立一对一的映射。编程语言
获取有用对象,必须以某种方式对对象进行请求,使对象完成各类任务。类型决定接口,而接口决定对象能知足的请求。就好比鸟类型,其提供的接口有飞翔,所以其能知足飞翔的请求。在接口肯定了某一特定对象可以发出的请求后,接口的实现掌控着请求的具体行为的展示方式。在类型中,每个可能的请求都有一个方法与之关联,当向对象发送请求时,与之关联的方法就会被调用。函数
如下代码是获取一个对象并调用其中的方法实例(你能够暂时不用理解,只须要知道形式便可,后面再反过来看就好)工具
public class Light { //开灯 public void on() { System.out.println("Light is on!"); } //关灯 public void off() { System.out.println("Light is off!"); } //这里是获取一个对象并调用其中方法的实例 public static void main(String [] args) { //这里是核心代码,开灯操做 Light lt = new Light(); lt.on(); } }
### 对象是服务提供者
程序经过调用其余对象提供的服务来向用户提供服务。程序员的目标就是去建立(或者最好是从现有的代码库中寻找)可以提供解决问题所需服务的一系列对象。
为何要把对象看做是服务提供者呢?
将程序开发人员按照角色划分为类建立者
和客户端程序员
。类建立者建立新的数据类型,而客户端程序员在其应用中使用类建立者建立的新数据类型。如此一来,客户端程序员的主要目的就是收集各类用来实现快速应用开发的类;而类建立者的目的则是构建类,并向客户端程序员暴露必须的部分。
为何类建立者须要对类的某些部分进行隐藏呢?或者说,为何须要进行访问权限控制呢?
修饰符 | 类内部 | 同包 | 子类 | 任何地方 |
---|---|---|---|---|
private | √ | × | × | × |
无 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
代码复用是面向对象程序设计语言所提供的最了不得的优势之一。
代码复用的基本方式:
关系是指事物之间存在单向或者相互的做用力或者影响力的状态。
在两个类之间存在有关系和不要紧两种状况,在有关系的状况下,其关系包括如下六种类型
类关系 | 英文名 | 描述 | 强权方 | UML图表示 | 示例说明 |
---|---|---|---|---|---|
继承 | extends | 父类与子类之间的关系:is-a | 父类 | 空心三角+实线,空心三角指向父类 | 鸟是动物 |
实现 | implements | 接口与实现类之间的关系:can-do | 接口 | 空心三角+虚线,空心三角指向接口 | 鸟实现了飞翔的接口 |
组合 | composition | 比聚合更强的关系:contains-a | 总体 | 实心菱形+实线,实心菱形指向总体 | 人类的头和身体是强组合关系 |
聚合 | aggregation | 暂时组装的关系:has-a | 组装方 | 空心菱形+实线,空心菱形指向组装方 | 狗和牵狗的绳子是聚合关系 |
依赖 | dependency | 一个类依赖于另外一个类:depends-a | 被依赖方 | 箭头+虚线,箭头指向被依赖方 | 人喂小狗,小狗是喂这个动做的被依赖方 |
关联 | association | 类与类之间存在互相平等的使用关系:links-a | 平等 | 实线 | 人与信用卡的关系,人用信用卡,信用卡能够读取我的信息 |
以现有类为基础,复制它,而后经过添加和修改这个副原本建立新类。当源类发生变化时,被修改的副本也会反应出这种变更。生物学中对科目的定义,用于解释继承关系再恰当不过。
父类:又称源类、基类、超类;
子类:又称导出类、继承类;
父类和子类之间的类型层次结构同时体现了他们之间的类似性和差别性。当继承现有类型时,也就创造了新的类型,同时子类又归属于父类的类型。这个新的类型不只包括现有类型的全部成员,并且更重要的是它复制了父类的接口,这意味着全部对父类对象的调用同时可能够对子类对象发起,这遵循了编程原则之一的里氏替换原则。
若是只是简单地继承一个类而不作其余任何事情,那么在父类接口中的方法将会直接继承到子类中。当须要使父类和子类产生差别时,有如下两种方式:
那么继承是否应该只覆写父类的方法呢?
若是继承只覆写了父类的方法,那么子类对象能够彻底替代父类对象,这一般称之为替代原则
,在这种状况下的类关系称为is-a
;但有时又的确须要在子类中添加新的接口,这种状况下父类没法访问新添加的接口,这种状况下类关系为is-like-a
,这时这种父类与子类之间的关系,被视为非存粹替代
在处理类层次关系的时候,若是把任意一个特定类型的对象能够看成其基类对象来对待,就使得人们能够编写出不依赖于特定类型的代码。
前期绑定:编译器将产生对一个具体函数名字的调用,而在运行时须要将这个调用解析到将要被执行的代码的绝对地址(意味着运行前就须要知道具体代码的位置)。
后期绑定:编译器只确保调用的方法存在,并且调用参数和返回值类型正确;在运行时,经过特殊代码,解析具体将要执行的代码的具体位置。
经过导出新的子类而轻松扩展设计的能力,是对改动进行封装的基本方式之一。
在试图将子类对象看成其基类对象来看待时,须要解决的一个问题是:编译器没法精确地了解哪一段代码将会被执行。在OOP程序设计中,程序直到运行时才可以肯定代码的位置。
OOP程序设计语言使用了后期绑定的概念:编译器确保调用方法的存在,并对调用参数和返回值执行类型检查,但并不知道将被执行的确切代码。Java使用一小段特殊的代码来替代绝对地址调用,这段特殊代码
用来计算方法体的具体位置。Java默认是动态绑定的。
把子类对象看做父类对象的过程,称做向上转型
。缘由是在类图中,父类老是位于类图的顶部,把子类对象视为父类对象,即将子类类型向上推导。
Java中全部的类最终都继承自单一的基类:Object
单根继承结构保证全部对象都具有某些功能。Object是任何类的默认父类,是在哲学方向上继续宁的延伸思考。
一般来讲,若是不知道在解决某个特定问题时须要多少对象,或者他们将存活多久,那么就不可能知道如何存储这些对象。
在Java标准类库中提供了大量容器。不一样的容器提供了不一样类型的接口和外部行为,同时对某些操做具备不一样的效率。如List中的ArrayList和LinkedList因为底层实现的不一样,具有不一样的应用场景。
因为容器只存储Object,因此将对象引入置入容器时,被向上转型为Object,在取出类型时会丢失其类型。在必定程度上可使用向下转型的方式来获取其实际类型,可是这样作存在风险。
package a; import java.util.*; public class Container { public static void main(String [] args) { List list = new ArrayList(); list.add("Hello World!"); list.add(1); //程序运行到这里是不会报错的,可是执行下面这一步的时候,就会出现异常了 for(Object o : list) { //这一步会出现异常,由于List中存放的不只仅是String类型,还有Integer类型,向下转型出现异常 String a = (String) o; System.out.println(a); } } }
那么用什么方式使容器记住这些对象究竟使什么类型呢?解决方案称为参数化类型,在Java中也称为泛型。表示方法为一对尖括号,中间包含类型信息。
List<String> list = new ArrayList<String>();
这样一来,就限定了List中只能存放String类型的对象啦!固然,咱们仍是可以经过反射绕过这层验证,毕竟在编译后运行时,是去泛型的。
在使用对象时,最关键的问题之一即是他们的生成和销毁方式。
new Constructor();:经过new关键词向堆中申请内存,经过Constructor来讲明类的建立方式。
Java采用动态内存分配
的方式。动态方式有个通常性假设:对象趋于变得复杂,因此查找和释放内存空间的开销不会对对象的建立形成重大冲击。动态方式所带来的更大的灵活性是解决通常化编程问题的要点。
Java提供了被称为垃圾收集器
的机制,用来处理内存释放问题。垃圾收集器的运行基础是单根继承结构
和只能在堆上建立对象
的特性。
错误处理始终是编程的难题之一。
异常是一种对象,其从出错点被抛出
,并被特定类型的异常处理器所捕获
。异常处理就像与程序正常执行路径并行的、在错误发生时执行的另外一条路径。
异常不能被忽略,它保证必定会在某处获得处理。异常提供了一种从错误状态进行可靠恢复的途径。Java一开始就内置了异常处理,并强制你必须使用它。它是惟一可接受的错误报告方式。
在计算机编程中,存在着在同一时刻处理多个任务的思想。这些彼此独立运行的部分称为线程,同一时刻处理多个任务称为并发。
在单一处理器中,线程只是一种为单一处理器分配执行时间的手段,换而言之,若是只有一个处理器,那么多线程程序的运行不过是多个任务竞争使用处理器的性能。在多处理器的状况下,实现的才是真正意义上的并发,多处理器并行计算。
多线程同时存在一个隐患,在存在共享资源的时候,可能会形成资源之间的竞争,进而形成死锁。因此在多线程修改共享资源时,必然在共享资源使用期进行锁定。
在Java中,JDK1.5后提供了concurrent包支持更好的并发特性。
以上是对象导论的一些基本概念,是继续阅读后面章节的非必要补充性材料。对于文中的一些代码段或概念,暂时不理解的,能够先放一放,等后面看完了,再回过来看就恍然大悟了。
下一节将讲解对象并写第一个Java程序。若是你对这些内容不感兴趣,想看点更高难度的,欢迎关注个人博客:https://www.cnblogs.com/lurke...;欢迎关注个人微信公众号JavaCorner