【JavaSE】面向对象三大特征——封装、继承、多态
前言:本文主要介绍思想
封装
封装这一概念并不仅存在与面向对象中,甚至说封装这一概念不仅限于编程中,其实生活中的封装无处不在。比如
需求:你到银行取钱
参数:你只需要提供银行卡和密码
返回值:柜员会将现金取给你。
方法:至于柜员在柜台后面如何验证你的密码、余额,又是如何拿到现金给你,你都不知道也无需知道
———— 这就是封装
封装说白了就是隐藏细节
生活中的例子帮我们理解概念,我们再来看一下程序中的封装
Arrays.sort(arr);
你调用库中的某个方法,来实现某个功能,你只需要传入正确的参数,即可让方法运行,达到你想要的结果,至于方法内部进行了怎样的操作你不知道也无需知道
——这就是封装,方法封装了算法的细节
对于对象来说,访问权限其实也是封装的一种体现
public class User{
private Long id;
public Long getId(){
return id;
}
public void setId(Long id){
this.id=id;
}
}
比如:
我们经常用private 修饰成员变量,然后选择性地提供public 方法以供外部访问成员变量。
在初学时可能会觉得多此一举,明明可以直接操作成员变量,为啥还要通过get、set方法来操作
举个例子
将phone字段封装起来,外部在调用set方法设置手机号时,便可以限制手机号的长度
然后调用get方法时,可以隐去关键信息,让手机号的中间四位数变成*号显示
这里就可以体现出封装的好处:
它可以对成员进行更精准的控制
让对象和调用者解耦类内部的结构和实现可以自由修改
同时也能保证数据的安全性、有效性
继承
继承中有两个非常重要的概念,搞懂这两个概念也就能熟练掌握继承了
is-a
is-a,中文称为“是一种”,简单来说,如果你可以这样描述
Dog is a Animal? yes
Cat is a Animal? yes
Apple is a Animal? no
B 是一种 A,那么B 就继承了 A
能通过is-a 测试,在逻辑上和程序中就能顺利的使用继承关系
拿猫叫狗叫动物叫举例,比如现在Dog和Cat类继承了Animal类
然后来看一下这三段代码的编译结果,前两段代码通过了is-a 测试,自然是没有问题,而第三段代码就没有通过is-a测试,所以编译失败。编译器进行了语法检查,检查的方式是从=号右边往左读,右边是不是左边或者右边是不是左边的子类,如果是编译器就会通过,这三段代码不管是从逻辑上还是程序上都好理解。
Animal animal = new Dog();// 编译成功
Dog dog = animal; // 编译失败
animal 变量是用Animal 类来做声明的,自然而然编译器就将其认定为Aniaml类,尽管第二行指向的是Animal的子类Dog,但animal变量的身份现在是Animal类,无法通过is-a 测试导致编译失败
编译器会检查父子类之间的is-a 的关系。
如果你不想要让编译器啰嗦
Animal animal = new Dog();
Dog dog=(Dog)animal;
可以加上强制类型转换,让编译器住嘴,编译器就让这段代码通过了编译,不过后果得自行负责
子类之间不能使用强制类型转换
总结:使用is-a原则就可以判断何时编译成功,何时编译失败,并留意指向的对象类型,就可以判断何时类型转换成功,何时会抛出异常,然后在使用继承前,也可以在逻辑上判断是否该使用继承。
狗是动物嘛?是的,所以狗继承动物没有一点问题。
猫是动物嘛?是的,所以猫继承动物也没问题。
苹果是动物嘛?不是,这在逻辑上说不过去,程序中也不应该让苹果继承动物类,否则会导致程序的整体逻辑混乱
当然你完全可以让Apple extends Animal,但这样是完全不合理的
作为开发者应当避免这种乱继承的行为
has-a
has-a 中文意思是“有一个”,它可以用来判断,类与成员的关系,比如
public class Bird extends Animal{
private String wings;
}
鸟是一种动物,那它可以继承Animal类这是is-a,鸟有翅膀,那Bird(鸟)类可以定义一个成员变量为翅膀,这个翅膀的成员变量就不适合定义在Animal类中也不适合定义在Dog和Cat类中
如果我们想设计出一个职责清晰的类,就需要好好的思考has-a关系。
总结:
is-a 帮助我们判断父子类的关系
has-a 帮助我们判断类与成员变量的关系
多态
三大特性的最后一个,也是最重要的一个。多态顾名思义:多种形态,听起来有点抽象。
假设你开了家宠物店,给宠物提供洗澡服务,于是你吩咐店员贴一个公告,咱们店可以让所有的哈士奇洗澡,结果都带着哈士奇来洗澡了。用代码表示就是宠物店提供了一个方法
// 参数是哈士奇,执行的功能是洗澡
public void shower(哈士奇 a);
但好像有点问题,有居民带了一只金毛过来
shower(哈士奇类的对象); //通过,因为转递进来的参数是匹配的
shower(金毛类的对象); //报错,因为传递进来的参数不是哈士奇
shower(萨摩耶类的对象); //报错,因为传递进来的参数不是哈士奇
shower(猫类的对象); //报错,因为传递进来的参数不是哈士奇
店员不允许金毛进店,因为你吩咐的是让哈士奇洗,金毛并不是哈士奇,所以不行,然后其他居民带着萨摩耶,柴犬啥的过来,店员都把他们拒之门外,你失去了很多顾客,但你还没办法生气,因为店员尽责的执行了你下达的命令。
这个店员就是编译器,语法不对的地方,它就让你编译失败。
你发现你好像做了一件傻事,明明自己开的是一个宠物店却只规定哈士奇过来洗澡,那生意自然少了很多。
你决定要让各种各样的宠物,哈士奇也行金毛也行
// 方法重载
public void shower(哈士奇,a); //参数是哈士奇
public void shower(哈士奇,a); //参数是金毛
public void shower(哈士奇,a); //参数是萨摩耶
...
...
...
因为宠物品种实在是太多了,你必须考虑各种各样的品种,于是你定义了一万个方法。
然而你发现,这样的成本实在是太高也太麻烦了,问题虽然能够解决,但也导致你筋疲力尽。
于是你决定改变,你重新吩咐店员只要是宠物(动物)过来,就可以享受洗澡服务
//参数是宠物(动物)类,执行的功能是洗澡
public void shower(Animal a);
这个指令一下达,生意立马就好了
shower(哈士奇类的对象); //通过,哈士奇类继承了Animal
shower(金毛类的对象); //通过,金毛类继承了Animal
shower(萨摩耶类的对象); //通过,萨摩耶类继承了Animal
shower(猫类的对象); //通过,猫类继承了Animal
因为别人不管是带哈士奇还是萨摩耶,甚至带猫过来都可以,用程序描述就是这段代码
这就是多态的一种体现,你只定义了一个方法和一个参数类型,但是传进来的参数却各种形态都有。
是不是非常方便,通过刚才的例子,我们可以总结一下多态的必要条件:
- 要有继承
- 父类引用指向子类对象
你参数定义的是Animal类,传进来的参数虽然可以多种多样,但是不能瞎传,得是Animal的子类才可以。
参数类型是父类,传进来的却是子类,这就上面继承说的is-a原则所体现出来的父类的引用指向子类的对象。
这些理解后我们再来深入一些
现在你的宠物点决定扩展业务了,不光要提供洗澡的服务,还有提供进食的服务。
// 宠物类
class Animal{
//进食方法
public void eat(){
System.out.pritln("宠物吃东西啦~");
}
}
// 你学聪明了,参数一开始设置的就是宠物类,这样所有宠物都可以进来
public void helpEat(Animal a){
// 调用宠物本身的进食方式
a.eat(); //打印的结果自然是"宠物吃东西啦~"
}
宠物进食自然是宠物的行为,在程序中行为就是方法,然后你照看宠物进食,并不是你进食,所以宠物来到宠物店后,那就要让宠物自己进食,即调用宠物的进食方法,这么一看好像特别完美。
但你立马又发现问题了,每个不同的宠物品种喜欢吃的东西不一样,吃的方式也不一样,可你现在调用的是单一的宠物进食方法,不管是什么宠物进来,打印的都是一句话,这就很尴尬了,因为狗是这种吃法,猫是另一种吃法,在你这就变成了同一种吃法。
要解决这个问题,就应该体现出,每个宠物吃法的特性。
class 哈士奇 extends Animal {
@Override
public void eat(){
System.out.pringln("哈士奇开始吃狗粮啦~");
}
}
class 狸花猫 extends Animal {
@Override
public void eat(){
System.out.pringln("哈士奇开始吃猫粮啦~");
}
}
class 东北虎 extends Animal {
@Override
public void eat(){
System.out.pringln("东北虎开始吃肉啦~");
}
}
自身的特性用程序语言描述就是方法的重写嘛,各品种的宠物重写了方法后
helpEat(哈士奇对象); // 传进来的参数类型是哈士奇对象,打印结果是:"哈士奇开始吃狗粮啦~"
helpEat(狸花猫); // 传进来的参数类型是狸花猫对象,打印结果是:"狸花猫开始吃猫粮啦~"
helpEat(东北虎); // 传进来的参数类型是东北虎对象,打印结果是:"东北虎开始吃肉啦~"
宠物店再进行服务的时候,效果就立马不一样了,这里就可以看出多态的好处,当父类的引用指向子类的对象时,调用的方法是子类重写后的方法,即体现了多种类型的传递,又体现了不同类型的特性,即复用了父类的属性和方法又扩展了自己的逻辑,所以继承所使用的关键字是 extends 而不是 inherit 更着重延申和扩展的意思,注意哦,这里的扩展完全不会影响原有逻辑,你宠物店的方法定义好后,无论宠物类的子类怎么扩展,对于当前功能来说,你都无需修改原代码,这也就是常说的开闭原则,对修改关闭,对扩展开放。
可以说多态就是面向对象的核心,它将扩展性达到了最好
到此面向对象三大特性就说完了。
在学习面向对象的时,语法没什么了不起的。
我们学习的应该是设计思路和理念,不是说用了对象就真的复用和扩展了。
如果不经思考滥用,不仅不能发挥面向对象的好处反而会让代码难以理解和难以维护。
代码设计是一门学问,也是一门艺术。
做出好的设计需要我们不断见识、不断思考、不断积累。
共勉!
【JavaSE】面向对象三大特征——封装、继承、多态的更多相关文章
- JAVA的三大特征 封装继承多态- 简单总结
简单总结一下 封装-即从很多类的抽取相同的代码 写在一个类里. 好处是 代码的重用,安全. 继承-减少代码的书写. 其好处也是 代码的重用. 多态- 把不同的子类对象都当作父类来看,可以屏蔽不同子类对 ...
- 深入理解Java面向对象三大特性 封装 继承 多态
1.封装 封装的定义: 首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系 为什么需要封装: 封装符合面向对象设计原则的第一条:单一性原则,一个类 ...
- Java三大特性(封装,继承,多态)
Java中有三大特性,分别是封装继承多态,其理念十分抽象,并且是层层深入式的. 一.封装 概念:封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据 ...
- OOP三大核心封装继承多态
OOP支柱 3 个核心:封装 继承 多态 封装就是将实现细节隐藏起来,也起到了数据保护的作用. 继承就是基于已有类来创建新类可以继承基类的核心功能. 在继承中 另外一种代码重用是:包含/委托,这种重用 ...
- -1-2 java 面向对象基本概念 封装继承多态 变量 this super static 静态变量 匿名对象 值传递 初始化过程 代码块 final关键字 抽象类 接口 区别 多态 包 访问权限 内部类 匿名内部类 == 与 equal
java是纯粹的面向对象的语言 也就是万事万物皆是对象 程序是对象的集合,他们通过发送消息来相互通信 每个对象都有自己的由其他的对象所构建的存储,也就是对象可以包含对象 每个对象都有它的类型 也就是 ...
- C++三大特性 封装 继承 多态
C++ 三大特性 封装,继承,多态 封装 定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对 ...
- 面向对象三大特征之继承(extends)——Java笔记(六)
继承: 从一般到特殊的关系,是一种拓展关系,子类对象是父类的一种,也可称为”is a“的关系 泛化: 把子类里的共性抽取到父类里的来的过程 特化: ...
- JAVA 面向对象 三大特征:继承
什么是继承 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可. 多个类可以称为子类,单独这个类称为父类.超类或者基类. 子类可以直接访 ...
- Java 面向对象三大特征之一: 多态
多态与类型转换 子类重写父类方法 1)位置:子类和父类中有同名的方法 2)方法名相同,返回类型和修饰符相同,参数列表相同 方法体不同 多态的优势和应用场合 多态:同一个引用类型,使用不同的 ...
随机推荐
- Slf4j的MDC初尝试
为什么会用到MDC? 本人使用Java两年时间,鉴于经验有限,在开发java后端代码过程中,为了定位问题,希望同一个线程的requestId可以从web层的日志一直输出到dao层,这样使用Linux命 ...
- java基础———break,continue
break通常用在循环语句之中用来跳出循环: continue终止某次循环过程,跳过尚未执行的语句:接着执行下次是否执行循环的判定:
- VS Code中Markdown常用插件
目录 目录 1.Markdown All in One 2.Markdown Preview Enhanced 3.markdownlint 1.Markdown All in One 自动生成目录 ...
- KingbaseES V8R6单实例外部备份故障案例
案例说明: 在KingbaseES V8R6单实例环境,配置外部备份服务器使用sys_backup.sh物理备份时,出现以下"WAL segment xxx was not archived ...
- Spring_事务总结
Spring 事务总结 rollbackFor 设为 Exception.class场景下 如果在函数内部catch住异常消费掉,没有再抛出的话,不会回滚 如果catch住 然后原封不动抛出,会回滚 ...
- 使用Kali的wifite和aircrack-ng联合破解wifi密码
准备材料 有kali的虚拟机,这里推荐VM 一个超级便宜的USB无线网卡,很便宜三十几块钱 一个靠谱的WPA密码字典(关于字典文件,我这里整理了好多,可联系我.QQ:1213456261) 1.运行k ...
- 2021年3月-第02阶段-前端基础-Flex 伸缩布局-移动WEB开发_流式布局
移动web开发流式布局 1.0 移动端基础 1.1 浏览器现状 PC端常见浏览器:360浏览器.谷歌浏览器.火狐浏览器.QQ浏览器.百度浏览器.搜狗浏览器.IE浏览器. 移动端常见浏览器:UC浏览器, ...
- [apue] 标准 I/O 库那些事儿
前言 标准 IO 库自 1975 年诞生以来,至今接近 50 年了,令人惊讶的是,这期间只对它做了非常小的修改.除了耳熟能详的 printf/scanf,回过头来对它做个全方位的审视,看看到底优秀在哪 ...
- ProxySQL介绍
介绍 ProxySQL是用C++语言开发的,一个轻量级开源软件,性能和功能满足读写中间件所需的绝大多数功能,其配置数据基于SQLite存储,目前已到v2.4.1版本. 功能方面如下: 最基本的读/写分 ...
- 【ProxySQL】ProxySQL Cluster的搭建
文章转载自:https://blog.51cto.com/l0vesql/2104643 背景 早期的ProxySQL若需要做高可用,需要搭建两个实例,进行冗余.但两个ProxySQL实例之间的数据并 ...