(尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/84592274冷血之心的博客)

目录

(一)封装

封装的定义

封装的好处

(二)继承

继承的概念

子类的特点

构造函数

覆盖/重写的概念

(三)多态

多态的概念

向上转型

向下转型

总结

结束语


面向对象的语言有三大特性,即封装继承与多态。三大特性是面向对象编程的核心,对于初学者务必加强对三大特性的理解与领会。在这篇博客中,我们通过具体的案例来依次阐述封装继承与多态的概念与使用。

(一)封装

封装的定义

何为封装?

把事物抽象成一个类,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系。当内部的逻辑发生变化时,外部调用不用因此而修改,它们只调用开放的接口,而不用去关心内部的实现。

封装的好处

  • 实现了专业的分工,将处理逻辑封装成一个方法,做到见名知其义
  • 良好的封装能够减少耦合
  • 隐藏信息,实现细节

接下来我们举两个封装的具体案例来进行阐述。

案例一:

package com.pak1;public class Test {public static void main(String[] args) {Student student = new Student();student.name = "小明";student.age = 16;student.printStudentAge();Student student2 = new Student();student2.name = "小白";student2.age = 120;student2.printStudentAge();}
}class Student {String name;int age;public void printStudentAge() {System.out.println(name + "同学的年龄:" + age);}
}

输出如下:

这个时候我们可以看到输出的小白同学的年龄120明显不科学,所以我们需要做一些内部逻辑的处理。所以需要进行代码封装,将内部逻辑进行一个隐藏。

封装之后的代码如下:

package com.pak1;public class Test {public static void main(String[] args) {Student student = new Student();student.setName("小明");student.setAge(16);student.printStudentAge();Student student2 = new Student();student.setName("小白");student.setAge(120);student2.printStudentAge();}
}class Student {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {if (age < 0 || age > 60)throw new RuntimeException("年龄设置不合法");this.age = age;}public void printStudentAge() {System.out.println(name + "同学的年龄:" + age);}
}

输出结果如下:

我们将Student这个类的name和age私有化,提供了公共的get/set方法才能进行访问,在get/set方法中我们可以对内部逻辑进行封装处理,外部的调用方不必关心我们的处理逻辑。

我们再来看另一种封装,在一个方法中,太多的逻辑将会导致该方法违反了单一性,导致该方法的可读性变差,这个时候我们需要将可以拆成一块的方法进行拆分,建立相应的service或者utils来封装该方法。举例如下:

案例二:

package com.pak1;public class TestFZ {public static void main(String[] args) {int score = 78;if (score > 100 || score < 0) {System.out.print("对不起,你的分数输入有错误");} else if (score > 89) {System.out.print("奖励你一台手机");} else if (score > 79) {System.out.print("奖励你一副耳机");} else if (score > 59) {System.out.print("奖励你一朵小红花");} else if (score > 59) {System.out.print("奖励你一朵小红花");}else {System.out.print("奖励你一个耳光");}}
}

好了,这部分的处理逻辑很复杂,看起来可读性也很差,这个时候我们需要封装。封装之后的代码如下:

package com.pak1;public class TestFZ {public static void main(String[] args) {int score = 78;TestFZ testFZ = new TestFZ();testFZ.printReward(score);}public void printReward(int score) {if (score > 100 || score < 0) {System.out.print("对不起,你的分数输入有错误");} else if (score > 89) {System.out.print("奖励你一台手机");} else if (score > 79) {System.out.print("奖励你一副耳机");} else if (score > 59) {System.out.print("奖励你一朵小红花");} else if (score > 59) {System.out.print("奖励你一朵小红花");} else {System.out.print("奖励你一个耳光");}}
}

这样我们便把处理各个分数段奖励的逻辑进行了封装,增强了可读性,并且外部接口不需要了解内部如何实现。

(二)继承

继承的概念

继承是面向对象的最显著的一个特征。继承是从已有的类(父类或者超类)中派生出新的类(子类),新的类能吸收已有类的数据属性和行为,并能扩展新的能力(方法的覆盖/重写)。JAVA不支持多继承,一个类只能有一个父类。父类是子类的一般化,子类是父类的特殊化(具体化)

子类的特点

  • 子类拥有父类非private的属性和方法
  • 子类可以添加自己的方法和属性,即对父类进行扩展
  • 子类可以重新定义父类的方法,即方法的覆盖/重写

构造函数

  • 构造函数不能被继承,子类可以通过super()显示调用父类的构造函数
  • 创建子类时,编译器会自动调用父类的无参构造函数
  • 如果父类没有定义无参构造函数,子类必须在构造函数的第一行代码使用super()显示调用

由于后边要阐述多态,这里我们先来介绍一个重要的概念,即方法的覆盖/重写

覆盖/重写的概念

当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆盖。

可以这么理解:重写就是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。这样,就可以实现对父类方法的覆盖。如果子类将父类中的方法重写了,而我们想调用父类中的同名方法怎么办?此时,通过使用super关键就可以实现这个功能,super关键字可以从子类访问父类中的内容,如果要访问被重写过的方法,使用“super.方法名(参数列表)”的形式调用。

下边我们给出一个案例,子类扩展了父类的行为,并且重写了父类中的方法。代码如下:

package com.pak1;public class Animal {private String name;private int age;public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public String getName() {return this.name;}public int getAge() {return this.age;}@Overridepublic String toString() {return "{" +"name='" + name + '\'' +", age=" + age +'}';}
}class Dog extends Animal {// 对父类中方法的一种重写public String getName() {return super.getName() + "Dog";}// 狗叫和狗吃饭都是在扩展父类中的行为public void voice() {System.out.println(super.getName() + " 汪");}public void eat() {System.out.println(super.getName() + "吃东西");}
}class Main {public static void main(String[] args) {Dog dog = new Dog();dog.setName("大狗");dog.setAge(3);System.out.println(dog.toString());dog.eat();dog.voice();System.out.println(dog.getName()); //执行的是Dog中的getName方法}
}

(三)多态

多态的概念

多态的本质是:一个程序中同名的不同方法。在面向对象的程序设计中,多态主要有以下三种方式来实现。

  • 通过子类对父类方法的覆盖来实现多态
  • 通过一个类中方法的重载来实现多态
  • 通过将子类的对象作为父类的对象实现多态。

覆盖的概念我们在前面以及介绍了,接下来我们简单阐述下何为重载

  • 重载是指一个类里面(包括父类的方法)存在方法名相同,但是参数不一样的方法,参数不一样可以是不同的参数个数、类型或顺序
  • 如果仅仅是修饰符、返回值、throw的异常 不同,那么这是2个相同的方法

我们重点阐述第三种实现方法,即通过将子类的对象作为父类的对象实现多态

把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。(这句话是我理解第三种方法的关键,请仔细阅读理解)

继承是面向对象语言中一个代码复用的机制,简单说就是子类继承了父类中的非私有属性和可以继承的方法,然后子类可以继续扩展自己的属性及方法。

对象的引用型变量是具有多态性的,因为一个引用型变量可以指向不同形式的对象,即:子类对象作为父类对象来使用。在这里涉及到了向上转型和向下转型。

向上转型

子类对象转为父类,父类可以是接口。公式:Father f = new Son(); Father是父类或接口,Son是子类。

向下转型

父类对象转为子类。公式:Son s = (Son) f;

在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型。若Father f = new Father()那么不可以转换,运行会报错。

对于多态,我们先举一个例子。在一个单位中,有职工employee,职工中又有少数是管理者manager,管理者中又有一部分是领导。若小明是管理者manager类的对象,他也可以被看做是employee的对象,即他也可以被看做是一个职工,他同时具备着职工的所有属性。

public class testDuoTai {public static void main(String[] args) {Employee emp1=new Employee("小明",23, 1000);   //emp1是Employee的对象System.out.println(emp1.getInfo());Employee emp2=new Manager("小明",23, 1000,5000); //注意此处emp2是Manager类的对象System.out.println(emp2.getInfo());}}
//定义一个父类
class Employee
{String name;int age;float salary;Employee(){};Employee(String name,int age,float sal){this.name=name;this.age=age;this.salary=sal;              }String getInfo(){return "职工姓名:"+name+"年龄:"+age+"工资:"+salary;}
}
//定义一个子类
class Manager extends Employee
{float allowance;Manager(String name,int age,float sal,float aa){this.name=name;this.age=age;this.salary=sal;allowance=aa;}
}

输出结果如下:

接下来我们再来看一个经典的多态讲解案例,代码如下:

package com.pak2;class A {public String show(D d) {return ("A and D");}public String show(A a) {return ("A and A");}
}class B extends A {public String show(B b) {return ("B and B");}public String show(A a) {return ("B and A");}
}
class C extends B {}
class D extends B {}public class Test {public static void main(String[] args) {A a1 = new A();A a2 = new B();B b = new B();C c = new C();D d = new D();System.out.println("1--" + a1.show(b));System.out.println("2--" + a1.show(c));System.out.println("3--" + a1.show(d));System.out.println("--------------");System.out.println("4--" + a2.show(b));System.out.println("5--" + a2.show(c));System.out.println("6--" + a2.show(d));System.out.println("--------------");System.out.println("7--" + b.show(b));System.out.println("8--" + b.show(c));System.out.println("9--" + b.show(d));}
}

这段代码的输出是啥呢?大家可以先分析分析,然后再看正确答案。

好了,正确答案如下:

在分析结果之前,我们先来看相关的知识点概念:

  • 父类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法

产生多态时候,各个方法调用的优先级顺序由高到低依次为:

this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

我们来分析以下结果:

(1)System.out.println("1--" + a1.show(b));

首先a1是一个标准的A对象的引用变量,传入的参数为标准的 new B(),我们在A类的方法中找不到show(B b)的方法,接着去看A的超类是否有show(B b)的方法,发现A没有超类。那么去执行this.show((super)O),即A类中的show(A a)方法,所以输出为A and A

(2)System.out.println("2--" + a1.show(c));

同理,a1.show(c) C的父类是B,所以C也是A的子类,那么最后还是调用A类中的show(A a)方法,所以输出为A and A

(3)System.out.println("3--" + a1.show(d));

这个没什么好疑惑的,直接就是调用A类中的show(D obj),所以输出为A and D

(4)System.out.println("4--" + a2.show(b));

这个的结果输出就有点迷惑人了,小伙伴们会惊奇,为什么不是调用B类中的show(B b)方法输出B and B ?我们来解释下,a2是一个父类A的引用变量指向了一个子类B的对象,也就是说表面类型是A,实际类型是B。当我们调用方法的时候,首先从其表面类型里边寻找方法 show(B b)结果没有找到,那么按照调用优先级,我们最终会调用到this.show((super)O) 也就是说我们调用了A类中的show(A a)方法。按照上边所述的概念:当父类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。在当前的表面类型中找到了该方法show(A a),并且在子类(B)中也覆盖了该方法,那么最终会调用实际类型(B类)中的该方法show(A a),所以输出B and A

(5)System.out.println("5--" + a2.show(c));

这个分析和4中的保持一致

(6)System.out.println("6--" + a2.show(d));

这个没什么疑问,在A类中找到了该方法show(D d),并且子类没有覆盖该方法,所以直接调用A类中的方法,输出A and D

(7)System.out.println("7--" + b.show(b));

b.show(b)应该没什么疑问,会直接调用B类中的方法show(B b) 输出 B and B

(8)System.out.println("8--" + b.show(c));

根据方法调用优先级,最终会调用到B类中的方法show(B b) 输出 B and B

(9)System.out.println("9--" + b.show(d));

根据方法调用优先级,最终会调用到A类中的方法show(D d) 输出 A and D

这是多态学习中的一个经典案例,我们要牢记方法之间的调用优先级,并且要分清楚表面类型和实际类型,以及区分子类中是否进行了方法的覆盖等知识点,这样才能做到条理清晰。

总结

在这篇博客中,我们依次介绍了面向对象的三大特性,即封装继承与多态。针对多态,我们又详细分析了经典的案例,帮助大家学习理解,接下来我会将这篇文章录制成CSDN学院的免费课程(视频教学),CSDN学院搜索 “杨文强” 关注我的课程。

结束语

如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群824733818一起交流学习。本群给大家提供一个学习交流的平台,欢迎各位真正喜欢提问喜欢分享知识的小伙伴进来交流技术。

Java三大特性:封装,继承与多态相关推荐

  1. Day55-每日一道Java面试题-Java 面向对象编程三大特性: 封装 继承 多态

    Java 面向对象编程三大特性: 封装 继承 多态 封装 封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问.但是如果一个类没有 ...

  2. Java提高篇 —— Java三大特性之继承

    一.前言 在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事 ...

  3. Java面向对象三大特性(封装继承多态)解释及案例

    文章目录 包 包基本语法 命名规则 命名规范 导入包实例 访问修饰符 面向对象编程-封装 面向对象编程-继承 super关键词 super和this的比较 方法重写/覆盖 (override) 注意事 ...

  4. Java三大特性[封装,继承,多态]

    封装 封装是使类中的成员变量都是private,并提供public方法访问这些变量的技术 实现封装: 1).修改属性的可见性来限制对属性的访问. 2).为每个属性创建一对赋值方法和取值方法,用于对这些 ...

  5. 深入理解Java面向对象三大特性 封装 继承 多态

    1.封装 封装的定义: 首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系 为什么需要封装: 封装符合面向对象设计原则的第一条:单一性原则,一个类 ...

  6. java三大特性——封装,继承,多态

    1.封装 使用封装有三大好处: 1.良好的封装能够减少耦合. 2.类内部的结构可以自由修改. 3.可以对成员进行更精确的控制. 4.隐藏信息,实现细节. 5.封装我们也能够对对象的出口做出很好的控制, ...

  7. Java 三大特性之——继承

    继承(inheritance)是面向对象的重要概念.继承是除组合(composition)之外,提高代码重复可用性(reusibility)的另一种重要方式.我们在组合(composition)中看到 ...

  8. 初步理解Java的三大特性——封装、继承和多态

    声明:整理自网络,如有雷同,请联系博主处理 一.封装 封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被 ...

  9. java 三大特性理解_java 三大特性--封装、继承和多态理解

    java 三大特性--封装.继承和多态理解 封装 /** * 所谓封装,就是将对象具有的成员变量和成员函数包装和隐藏起来,让外界无法直接使用, * 被封装的成员只能通过某些特定的方式才能访问. * 实 ...

  10. java三大特性 继承_java基础(二)-----java的三大特性之继承

    在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...

最新文章

  1. 01 小程序开发入门
  2. 阿里云护航罗振宇2018“时间的朋友”跨年演讲,与千万观众一起跨年
  3. 在Eclipse下面安装spket
  4. python字符串注释_python字符串注释_Python学习笔记-字符串与注释
  5. linux中创建gpio节点
  6. 英特尔吃iPhone 7 Modem芯片订单可能逾5成 挹注15亿美元营收
  7. 有家创业公司花重金要请我当CTO?!
  8. vue-cli3.0升级失败,vue-cli卸载不掉,vue-cli升级不了3.0,
  9. Raki的读paper小记:Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks
  10. Android天天数钱游戏源码
  11. 宣讲会通知|香港科技大学工学院理学硕士线上直播综合场【MSc】
  12. HTML-CSS常用元素居中对齐方法
  13. 500报错及故障排除
  14. 华尔街最闪耀的新星:量子计算
  15. 北斗微信与服务器怎么联接,北斗卫星的导航服务全球性升级,手机怎样“连接”使用北斗导航?...
  16. 深度学习中降维和升维
  17. SegmentFault 技术周刊 Vol.30 - 学习 Python 来做一些神奇好玩的事情吧
  18. matlab 纵向的虚线,纵向减速标记符号中间是虚线可以变道吗
  19. 【纯干货】Vue仿蘑菇街商城项目(vue+koa+mongodb)
  20. 网络留言系统(Javaweb简易开发)

热门文章

  1. MATLAB permute命令的操作逻辑
  2. 微信小程序手机号解密
  3. spring boot大学生综合素质测评系统 毕业设计-附源码162308
  4. cm6.2配置sentry
  5. 前锋PHP咋样,最新说说燃气热水器前锋热水器A系列真的好吗,究竟怎么样呢?达人深度全面评测...
  6. springcloud:Fegin实现微服务间调用
  7. 应届程序员一年能挣多少钱?字节跳动员工:存款40万,感觉良好
  8. Unity的地形Splat图片
  9. 员工离职预测(logistic)(R语言)
  10. 安装配置无线 SONOS HIFI 系统 (完整版)