Java知识体系之面向对象
Java知识体系
怎么理解面向对象?
世界上有很多事物,每一个都可以看做一个对象,而每个对象都有自己的属性和行为,对象与对象之间也有各种关系。面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。
“属性” 包括对象的静态信息,如特性、状态等;“行为” 也就是动态信息,如对象的操作、功能等。
把具有相同属性和行为的对象归纳为类,类是一个抽象的概念,对象是类的具体。
面向对象的编程方法有四个基本特性:
- 抽象
将同一类事物的共同特征总结出来,构造成类的过程。包括数据抽象以及行为抽象。数据抽象变成类的属性,行为抽象变成类的方法。抽象只关心变量以及方法,并不关系具体的实现细节。
抽象更多的是一种思维,我们在编程的时候也要带入这种思维,比如getTaobaoOrder() 这个方法随着项目的拓展可能就不太适用了,用getOrder()会更好。
- 封装
把对象的数据和行为结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
可以理解为我们无论内部实现有多复杂,使用者可以完全不用理,只需要知道怎么使用就可以了。
封装的好处是:
- 隐藏实现细节,提供公共的访问方式
- 提高了代码的复用性
- 保证数据的安全性,防止调用者随意更改数据
- 继承
一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。
继承的好处:
- 代码复用,把共性的全部抽到父类,并且子类还可以扩展新的数据和行为,也可以通过重写来复写父类的行为。
- 多态
通俗的讲,就是同一个东西表现出多种状态。比如男孩,女孩都是人类,都是人类的不同状态。
多态指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
多态是和继承一脉相承的,多态存在的需要有三个必要条件:继承、重写、父类引用指向子类对象。
多态的好处:
便于接口的维护和拓展,可以将某一个子类切换成其他的子类,代码不需要做任何的改变,具有可替换性。
用个案例再补充解释一下:
比如说打印机,它属于一个父类,有一台彩色打印机,属于子类,有一台黑白打印机,也属于子类。
- 继承,彩色打印机和黑白打印机都继承自打印机这个父类;
- 重写,打印机有一个打印的方法,彩色打印机和黑白打印机会重写父类的打印的方法,效果就是,彩色打印机打印出来的效果是彩色,而黑白打印机打印出来的效果是黑白的。
- 父类引用指向子类对象,例如:Parent p = new Child() ;对应到打印机的案例,new 一个对象的时候,父类打印机的引用会指向具体的对象,彩色打印机或者黑白打印机。
类之间的关系:
- 依赖
对类 B 进行修改会影响到类 A 。
依赖关系是指一个类对另外一个类的依赖。这种关系是一种非常弱、临时性的关系。依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。
- 关联
对象 A 知道对象 B。类 A 依赖于类 B
在java语言中,关联关系一般表现为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量;
- 聚合
对象A知道对象B且由B构成。类A依赖于类B。强调的是整体与部分之间的关系,例如雁群和大雁的关系、书架和书之间的关系。
- 组合
对象 A 知道对象 B、由 B 构成而且管理着 B 的生命周 期。类 A 依赖于类 B。
它强调了整体与部分的生命周期是一致的,而聚合的整体和部分之间在生命周期上没有什么必然的联系。例如大雁和大雁的翅膀、人和手是组合关系。
- 实现
类 A 定义的方法由接口 B 声明。 对象 A 可被视为对象 B。类 A 依赖于类 B。
- 继承
类 A 继承类 B 的接口和实现, 但是可以对其进行扩 展。对象 A 可被视为对象 B。类 A 依赖于类 B。
总的来说,这几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖
面向对象的优点
面向对象的优点:可扩展性、可维护性和可重用性。
- 可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改。
- 可维护性:能够将功能与数据结合,方便管理。
- 可重用性:代码重复使用,减少代码量,提高开发效率。
面向对象 & 面向过程
面向过程是一种以过程为中心的编程思想,它首先分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,在使用时依次调用,是一种基础的顺序的思维方式。
面向过程开发方式是对计算机底层结构的一层抽象,它将程序分为数据和操纵数据的操作两部分,其核心问题是数据结构和算法的开发和优化。
面向对象是按人们认识客观世界的系统思维方式,采用基于对象(实体)的概念建立模型,模拟客观世界分析、设计、实现软件的编程思想,通过面向对象的理念使计算机软件系统能与现实世界中的系统一一对应。
面向对象方法直接把所有事物都当作独立的对象,处理问题过程中所思考的不再主要是怎样用数据结构来描述问题,而是直接考虑重现问题中各个对象之间的关系。
面向对象和面向过程的思想有着本质上的区别,当面对一个问题时:
- 对于面向过程而言,分析这个问题所需要的步骤,第一步先做什么,第二步再做什么。
- 对于面向对象来说,简单分为三步:
- 分析这个问题里面有哪些类和对象
- 分析这些类应该具有哪些属性和方法
- 最后分析类和类之间具体有什么关系
关于这个本质区别,我们举例说明一下,设计一个五子棋程序:
面向过程的设计思路是,首先分析问题的步骤:
- ① 开始游戏;
- ② 黑子先走;
- ③ 绘制画面;
- ④ 判断输赢;
- ⑤ 轮到白子;
- ⑥ 绘制画面;
- ⑦ 判断输赢;
- ⑧ 返回步骤 ②;
- ⑨ 输出最后结果。
面向对象的设计,则将程序分为三类对象:
- ① 黑白双方,这两方的行为是一模一样的
负责接受用户输入,并告知第②类对象(棋盘对象)棋子布局变化
- ② 棋盘系统,负责绘制画面
棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第 ③ 类对象(规则系统)来对棋局进行判定。
- ③ 规则系统,负责判定诸如犯规、输赢等
可见,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。功能上的统一保证了面向对象设计的可扩展性。
如要加入悔棋功能,若是面向过程设计,则从输入到判断到显示的若干步骤都要改动,甚至步骤之间的先后顺序都可能需要调整。而若是面向对象设计,则只需改动第 ② 类对象(棋盘对象)即可,棋盘对象保存了黑白双方的棋谱和落子先后顺序,简单回溯操作即可实现悔棋功能,并不涉及显示和规则部分,改动是局部可控的。
所以,我们总结一下,两者的优缺点:
- |
面向对象 |
面向过程 |
特性 |
抽象、继承、封装、多态 |
功能模块化,代码流程化 |
优点 |
易维护、易复用、易扩展、低耦合 |
性能高,适合资源紧张、实时性强的场合 |
缺点 |
性能比面向过程低 |
没有面向对象易维护、易复用、易扩展 |
可以这样理解:
- 面向过程,是一份蛋炒饭
米饭和鸡蛋混在一起炒匀,入味均匀,吃起来香。
- 面向对象,是一份盖浇饭
米饭和盖菜分别做好,”饭” 和”菜”的耦合度比较低;饭不满意就换饭,菜不满意换菜,”可维护性“比较好。
抽象&封装&继承&多态
抽象
将同一类事物的共同特征总结出来,构造成类的过程。包括数据抽象以及行为抽象。数据抽象变成类的属性,行为抽象变成类的方法。抽象只关心变量以及方法,并不关系具体的实现细节。
抽象更多的是一种思维,我们在编程的时候也要带入这种思维,比如getTaobaoOrder() 这个方法随着项目的拓展可能就不太适用了,用getOrder()会更好。
封装
把对象的数据和行为结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
可以理解为我们无论内部实现有多复杂,使用者可以完全不用理,只需要知道怎么使用就可以了。
封装的好处是:
- 隐藏实现细节,提供公共的访问方式
- 提高了代码的复用性
- 保证数据的安全性,防止调用者随意更改数据
继承
一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。
继承的好处:
- 代码复用,把共性的全部抽到父类,并且子类还可以扩展新的数据和行为,也可以通过重写来复写父类的行为。
多态
通俗的讲,就是同一个东西表现出多种状态。比如男孩,女孩都是人类,都是人类的不同状态。
多态指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
多态是和继承一脉相承的,多态存在的需要有三个必要条件:继承、重写、父类引用指向子类对象。
用个案例再补充解释一下:
比如说打印机,它属于一个父类,有一台彩色打印机,属于子类,有一台黑白打印机,也属于子类。
- 继承,彩色打印机和黑白打印机都继承自打印机这个父类;
- 重写,打印机有一个打印的方法,彩色打印机和黑白打印机会重写父类的打印的方法,效果就是,彩色打印机打印出来的效果是彩色,而黑白打印机打印出来的效果是黑白的。
- 父类引用指向子类对象,例如:Parent p = new Child() ;对应到打印机的案例,new 一个对象的时候,父类打印机的引用会指向具体的对象,彩色打印机或者黑白打印机。
多态的好处:
- 便于接口的维护和拓展,可以将某一个子类切换成其他的子类,代码不需要做任何的改变,具有可替换性。
Java为什么不支持多继承?
- 菱形继承问题
我们有两个类B和C从A继承。假设B和C覆盖了继承的方法,并且它们提供了自己的实现。现在D继承了B和C的多重继承。D应该继承该覆盖方法,将使用哪个覆盖方法?是来自B还是来自C?在这里,我们有歧义。
- 很少用,在实际开发过程中,多继承的实用场景很少
- 可以实用接口来间接实用多继承
为什么接口可以多继承呢?
接口全都是抽象方法,并没有具体实现,继承谁都无所谓,不存在菱形继承的问题,所以接口可以继承多个接口。
类的关系?
类之间的关系:
- 依赖
对类 B 进行修改会影响到类 A 。
依赖关系是指一个类对另外一个类的依赖。这种关系是一种非常弱、临时性的关系。依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。
- 关联
对象 A 知道对象 B。类 A 依赖于类 B
在java语言中,关联关系一般表现为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量;
- 聚合
对象A知道对象B且由B构成。类A依赖于类B。强调的是整体与部分之间的关系,例如雁群和大雁的关系、书架和书之间的关系。
- 组合
对象 A 知道对象 B、由 B 构成而且管理着 B 的生命周 期。类 A 依赖于类 B。
它强调了整体与部分的生命周期是一致的,而聚合的整体和部分之间在生命周期上没有什么必然的联系。例如大雁和大雁的翅膀、人和手是组合关系。
- 实现
类 A 定义的方法由接口 B 声明。 对象 A 可被视为对象 B。类 A 依赖于类 B。
- 继承
类 A 继承类 B 的接口和实现, 但是可以对其进行扩 展。对象 A 可被视为对象 B。类 A 依赖于类 B。
总的来说,这几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖
重载&重写
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private则子类中就不是重写。
区别点 |
重载 |
重写 |
参数列表 |
必须修改 |
一定不能修改 |
返回类型 |
可以修改 |
可以是当前返回类型/子类 |
异常 |
可以修改 |
可以减少/删除,一定不能抛出更广/新的异常 |
访问 |
可以修改 |
可以降低限制,例如(protected->public) |
访问修饰符
修饰符 |
当前类 |
当前包 |
子类 |
任何地方 |
private |
√ |
|
|
|
default |
√ |
√ |
|
|
protected |
√ |
√ |
√ |
|
public |
√ |
√ |
√ |
√ |
抽象类&接口
接口是对行为的抽象,它是抽象方法的集合,利用接口可以达到API定义和实现分离的目的。
接口,不能实例化;不能包含任何非常量成员,任何field都是隐含着public static final的意义;同时,没有非静态方法实现,也就是说要么是抽象方法,要么是静态方法。Java标准类库中,定义了非常多的接口,比如java.util.List。
抽象类是不能实例化的类,用abstract关键字修饰class,其目的主要是代码重用。
除了不能实例化,形式上和一般的Java类并没有太大区别,可以有一个或者多个抽象方法,也可以没有抽象方法。抽象类大多用于抽取相关Java类的共用方法实现或者是共同成员变量,然后通过继承的方式达到代码复用的目的。Java标准库中,比如collection框架,很多通用部分就被抽取成为抽象类,例如java.util.AbstractList。
设计层面
- 抽象类是对类抽象,包括属性、行为,而接口是对行为的抽象。
- 抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
- 抽象类所体现的是一种继承关系,是"is-a"关系。对于接口仅仅是实现了接口定义的契约而已,是"like-a"的关系。
- 抽象类主要是用于复用,接口主要是用来定义行为规范,达到解耦的目的。
语法
相同点
不同点
-- |
抽象类 |
接口 |
方法默认实现 |
支持 |
自1.8支持 |
实现 |
extends继承抽象类,实现抽象方法 |
implements实现接口的所有方法 |
是否有构造函数 |
是 |
否 |
方法的访问修饰符 |
public、protected和default |
public |
main方法 |
支持 |
不支持 |
添加方法 |
非抽象方法,子类不需要更改 |
自1.8可以有默认实现 |
成员变量 |
都可以 |
只能是常量 |
面向对象的原则
SOILD
- 单一职责
修改一个类的原因只能有一个。尽量让每个类只负责软件中的一个功能, 并将该功能完全封 装(你也可称之为隐藏)在该类中。
这条原则的主要目的是减少复杂度。
- 开闭原则
对于扩展,类应该是“开放”的;对于修改,类则应 是“封闭”的。
本原则的主要理念是在实现新功能时能保持已有代码不变。
- 里氏替换原则
当你扩展一个类时, 记住你应该要能在不修改客户端 代码的情况下将子类的对象作为父类对象进行传递。
这意味着子类必须保持与父类行为的兼容。 在重写一个方法 时, 你要对基类行为进行扩展, 而不是将其完全替换。
- 接口分离原则
客户端不应被强迫依赖于其不使用的方法。
尽量缩小接口的范围, 使得客户端的类不必实现其不需要的 行为。
- 依赖倒置原则
高层次模块,不应该依赖于低层次模块,而是应该基于抽象。
依赖倒置原则通常和开闭原则共同发挥作用: 你无需修改已 有类就能用不同的业务逻辑类扩展低层次的类。
面向对象,设计一个计算器
参考
https://blog.csdn.net/zzy0919sun/article/details/81171006
http://c.biancheng.net/view/7811.html
https://baijiahao.baidu.com/s?id=1677502245765548859&wfr=spider&for=pc
https://blog.csdn.net/notthin/article/details/121395289
https://blog.51cto.com/u_13604316/2729479
https://blog.csdn.net/qfguan/article/details/121226260
https://www.cnblogs.com/shudayoukuzhi/p/15703940.html