网站首页 > java教程 正文
1 概念
多态:一个事物的多种形态;一个对象具有多种形态。多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态(polymorphism)本来是生物学里的概念,表示地球上的生物在形态和状态方面的多样性。 而在java的面向对象中,多态则是指同一个行为可以有多个不同表现形式的能力。也就是说,在父类中定义的属性和方法,在子类继承后,可以有不同的数据类型或表现出不同的行为。这可以使得同一个属性或方法,在父类及其各个子类中,可能会有不同的表现或含义。比如针对同一个接口,我们使用不同的实例对象可能会有不同的操作,同一事件发生在不同的实例对象上会产生不同的结果。
实现方式:
父类的引用,创建子类对象。必须有继承,父类定义方法,子类重写方法。
- 方法的形参用父类类型,传入的实参用子类类型
- 使用父类类型声明对象,创建的是子类对象
class Pet{
public String name="pet";
public void speak(){
}
}
class Dog extends Pet{
public String name="dog";
@Override
public void speak(){
System.out.println("汪汪汪");
}
public void work(){
}
}
class Cat extends Pet{
public String name="cat";
@Override
public void speak(){
System.out.println("喵喵喵");
}
}
class Master{
public void speak(Pet pet){//形参是父类类型对象
pet.speak();
}
}
2 多态的优劣势
(1)优势
1、可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他 任何圆形几何体,如圆环,也同样工作。
2、可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
3、接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
4、灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
5、简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
(2)劣势
1、性能损失:在某些情况下,多态性可能会导致一定的性能损失。由于动态绑定需要在运行时确定方法的调用对象,因此会增加一定的运行时开销。特别是在对性能要求较高的场景下,多态性可能会影响程序的性能表现。
2、难以理解和调试:多态性使得程序的控制流程更加动态和复杂,这可能会增加代码的理解和调试难度。特别是对于大型项目或复杂的继承关系,理解代码的行为可能需要跟踪多层继承关系和动态绑定的调用关系,这可能会增加调试的复杂性。
3、运行时错误:由于多态性是在运行时确定方法的调用对象,因此在编译时无法完全确定方法调用的正确性。这可能导致一些运行时错误,特别是当程序中存在复杂的继承关系和动态绑定的情况时。为了避免这种错误,需要编写更加谨慎和健壮的代码。
4、设计复杂性:使用多态性可能会增加程序的设计复杂性。在设计阶段需要考虑如何定义合适的接口和抽象类,以及如何组织类之间的继承关系,这需要更多的设计和分析工作。特别是在面对复杂的业务需求和变化时,设计灵活且具有扩展性的类结构可能会增加额外的复杂性。
5、运行时效率:多态性的实现通常依赖于动态绑定和虚方法表,这可能会增加一定的内存消耗和运行时开销。特别是在资源受限的环境下,如嵌入式系统或移动设备上,多态性可能会对程序的运行效率产生一定的影响。
3 多态存在的三个必要条件
- 继承或实现:在多态中必须存在有继承或实现关系的子类和父类
- 方法的重写:子类对父类中的某些方法进行重新定义(重写,使用@Override注解进行重写)
- 基类引用指向派生类对象,即父类引用指向子类对象,父类类型:指子类对象继承的父类类型,或实现的父接口类型
4 虚方法和非虚方法
只有虚方法才能实现多态,使用的比较多的虚方法
4.1 非虚方法
方法在编译期,就确认了具体的调用版本,在运行时不可变,这种方法就称为非虚方法
- 静态方法:与类型直接关联
- 私有方法:在外部不可访问
- final修饰的方法:不能被继承
- 实例构造器(构造方法),通过super调用的父类方法
4.2 虚方法
静态分派:使用父类的引用调用方法
动态绑定:根据具体的运行时类型决定运行哪个方法
- 方法的参数在编译期间确定,根据编译器时类型,找最匹配的
- 方法的所有者如果没有重写,就按照编译时类型处理;如果有重写,就按照运行时类型处理
4.3 重写和重载中的方法调用
- 重写示例1
/*
1、编译期间进行静态分派:即确定是调用Animal类中的public void eat()方法,如果Animal类或它的父类中没有这个方法,将会报错。
2、运行期间进行动态绑定:即确定执行的是Cat类中的public void eat()方法,因为子类重写了eat()方法,如果没有重写,那么还是执行Animal类在的eat()方法
*/
abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
public class Test{
public static void main(String[] args){
Animal a = new Cat();
a.eat();
}
}
- 重载示例1
class Father{
}
class Son extends Father{
}
class Daughter extends Father{
}
class MyClassOne{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
public void method(Daughter d) {
System.out.println("daughter");
}
}
class MyClassTwo{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
}
public class TestOverload {
/*
1、编译期间进行静态分派:即确定是调用MyClassOne类中的method(Father f)方法。
2、运行期间进行动态绑定:确定执行的是MyClassOne类中method(Father f)方法
## 因为此时f,s,d编译时类型都是Father类型,因此method(Father f)是最合适的
*/ @Test
public void test() {
MyClassOne my = new MyClassOne();
Father f = new Father();
Father s = new Son();
Father d = new Daughter();
my.method(f);//father
my.method(s);//father
my.method(d);//father
}
/*
1、编译期间进行静态分派:即确定是分别调用MyClassTwo类中的method(Father f),method(Son s),method(Father f)方法。
2、运行期间进行动态绑定:即确定执行的是MyClass类中的method(Father f),method(Son s),method(Father f)方法
## 因为此时f,s,d编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配
*/ @Test
public void test() {
MyClassTwo my = new MyClassTwo();
Father f = new Father();
Son s = new Son();
Daughter d = new Daughter();
my.method(f);//father
my.method(s);//Son
my.method(d);//father
}
}
- 重载与重写示例1
class MyClass{
public void method(Father f) {
System.out.println("father");
}
public void method(Son s) {
System.out.println("son");
}
}
class MySub extends MyClass{
public void method(Daughter d) {
System.out.println("daughter");
}
}
class Father{
}
class Son extends Father{
}
class Daughter extends Father{
}
/*
1、编译期间进行静态分派:即确定是分别调用MyClass类中的method(Father f),method(Son s),method(Father f)方法。
2、运行期间进行动态绑定:即确定执行的是MyClass类中的method(Father f),method(Son s),method(Father f)方法。
## my变量在编译时类型是MyClass类型,那么在MyClass类中,只有method(Father f),method(Son s)方法。,s,d变量编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配。而在MySub类中并没有重写method(Father f)方法,所以仍然执行MyClass类中的method(Father f)方法
*/
public class TestOverload {
public static void main(String[] args) {
MyClass my = new MySub();
Father f = new Father();
Son s = new Son();
Daughter d = new Daughter();
my.method(f);//father
my.method(s);//son
my.method(d);//father
}
}
5 接口
5.1 接口的概述
接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的类 。
接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义与实现分离,优化了程序设计。
5.2 接口的格式与使用
(1)接口的格式
与定义类的 class 不同,接口定义时需要使用 interface 关键字。
定义接口所在的仍为 .java 文件,虽然声明时使用的为 interface 关键字的编译后仍然会产生 .class 文件。这点可以让我们将接口看做是一种只包含了功能声明的特殊类。
定义格式:
public interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
(2)接口的使用
- 接口中的方法全是抽象方法,直接 new 接口来调用方法没有意义,Java也不允许这样干。
- 类与接口的关系为实现关系,即类实现接口。实现的动作类似继承,只是关键字不同,实现使用 implements。
- 其他类(实现类)实现接口后,就相当于声明: 我应该具备这个接口中的功能。实现类仍然需要重写方法以实现具体的功能。
格式:
class 类 implements 接口 {
重写接口中方法
}
在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。
案例代码
/*
* Java语言的继承是单一继承,一个子类只能有一个父类(一个儿子只能有一个亲爹)
* Java语言给我们提供了一种机制,用于处理继承单一的局限性的,接口
*
* 接口:接口是一个比抽象类还抽象的类,接口里所有的方法全是抽象方法,接口和类的关系是实现,implements
* interface
*
* 格式:
* interface 接口名 {
*
* }
*
*/
public class InterfaceDemo {
public static void main(String[] args) {
BillGates gates = new BillGates();
gates.code();
}
}
class Boss {
public void manage() {
System.out.println("管理公司");
}
}
class Programmer {
public void code() {
System.out.println("敲代码");
}
}
//比尔盖茨
class BillGates extends Programmer {
}
5.3 接口中成员的特点
- 1、接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量,其值不能改变。后面我们会讲解fnal关键字
- 2、接口中可以定义方法,方法也有固定的修饰符,public abstract
- 3、接口不可以创建对象。
- 4、子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
案例代码
/*
* 接口的成员特点:
* 只能有抽象方法
* 只能有常量
* 默认使用public&abstract修饰方法
* 只能使用public&abstract修饰方法
* 默认使用public static final来修饰成员变量
*
* 建议:建议大家手动的给上默认修饰符
*
* 注意:
* 接口不能创建对象(不能实例化)
* 类与接口的关系是实现关系,一个类实现一个接口必须实现它所有的方法
*/
public class InterfaceDemo2 {
public static void main(String[] args) {
//Animal a = new Animal();
//Animal.num;
}
}
interface Animal {
public static final int num = 10;
public abstract void eat();
}
class Cat implements Animal {
public void eat() {
}
}
5.4 接口和类的关系
- A:类与类之间: 继承关系,一个类只能直接继承一个父类,但是支持多重继承
- B:类与接口之间: 只有实现关系,一个类可以实现多个接口
- C:接口与接口之间: 只有继承关系,一个接口可以继承多个接口
案例代码
/*
*
* 类与类:继承关系,单一继承,多层继承
* 类与接口:实现关系,多实现
* 接口与接口的关系:继承关系,多继承
*/
public class InterfaceDemo3 {
public static void main(String[] args) {
}
}
interface InterA extends InterB {
public abstract void method();
}
interface InterB {
public abstract void function();
}
interface InterC extends InterA {
}
class Demo implements InterC {
@Override
public void method() {
// TODO Auto-generated method stub
}
@Override
public void function() {
// TODO Auto-generated method stub
}
}
5.5 接口优点
- 1.类与接口的关系,实现关系,而且是多实现,一个类可以实现多个接口,类与类之间是继承关系,java 中的继承是单一继承,一个类只能有一个父类,打破了继承的局限性。
- 2.对外提供规则(USB接口)
- 3.降低了程序的耦合性(可以实现模块化开发,定义好规则,每个人实现自己的模块,提高了开发的效率)
6 抽象类和抽象方法
6.1 概述
类用于描述现实生活中一类事物。类中有属性、方法等成员。
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有 意义,而方法主体则没有存在的意义了。
某种情况下,父类只能知道子类应该具备一个怎样的方法,但是不能够明确知道如何实现该方法。只能在子类中才能确定如何去实现方法体。例如:所有几何图形都应该具备一个计算面积的方法。但是不同的几何图形计算面积的方式不同。
我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法 的类就是抽象类。
6.2 抽象方法
抽象方法 : 只有方法的声明,没有方法体,以分号 ; 结尾,使用 abstract 关键字修饰
定义格式:
修饰符 abstract 返回值类型 方法名(参数列表);
代码举例:
public abstract void run();
抽象方法不能用private、final、static、native修饰
6.3 抽象类
抽象类:包含抽象方法的类。如果一个类包含抽象方法,那么该类必须是抽象类,使用 abstract 关键字修饰
定义格式:
public abstract class 类名 {
//抽象类中可以包含变量、常量,抽象方法,非抽象方法
}
代码举例:
public abstract class Person {
public abstract void work();
}
抽象类的使用
抽象类不能实例化,不能直接创建对象。抽象类是用来被继承的,继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类,使用 abstract 关键字修饰
抽象类也是类,因此原来类中可以有的成员,抽象类都可以有,那么抽象类不能直接创建对象,为什么还有构造器呢?供子类调用,子类创建对象时,需要为从父类继承的属性初始化。
抽象类不能使用final修饰
public class Teacher extends Person {
public void work() {
System.out.println("讲课");
}
}
public class AbstractClassTest {
public static void main(String[] args) {
// 创建子类对象
Teacher t = new Teacher();
// 调用run方法
t.work();
}
}
输出结果:
讲课
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,叫做实现方法。
实现:去掉abstract关键字,加上方法体{...}
抽象类注意事项:
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 抽象类中,可以有成员变量。 理解:子类的共性的成员变量 , 可以定义在抽象父类中。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。 理解:未包含抽象方法的抽象类,声明为抽象类目的就是不想让使用者创建该类的对象,通常用于某些特殊的类结构设计。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译报错。除非该子类也是抽象类。 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
抽象类与普通类的区别
- 抽象类使用abstract修饰;普通类没有abstract修饰
- 抽象类不能实例化;普通类可以实例化
- 抽象类可以包含抽象方法,也可以包含非抽象方法;普通类不能有抽象方法
4. abstract关键字
可以用来修饰的结构:类、方法,不能用来修饰变量、代码块、构造器
不能和 abstract 一起使用的修饰符
外部类可用修饰符:abstract、final,两种访问修饰符:public和缺省。其中abstract和final不能一起修饰类
方法可用修饰符:4种访问修饰符,static、final、abstract、native。不能共存的:
- private,abstract不行 因为private不能被重写
- static,abstract不行 因为static不能被重写
- final,abstract不行 因为final不能被重写
- native,abstract不行 因为都没有方法体,不知道是什么情况,会有歧义
6.5 抽象类简单案例
6.5.1案例介绍
某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部员工、维护部员工)。研发部根据所需研发的内容不同,又分为JavaEE工程师、Android工程师;维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师。
公司的每名员工都有他们自己的员工编号、姓名,并要做它们所负责的工作。
工作内容:
- JavaEE工程师: 员工号为xxx的 xxx员工,正在研发淘宝网站
- Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件
- 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
- 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机
请根据描述,完成员工体系中所有类的定义,并指定类之间的继承关系。进行XX工程师类的对象创建,完成工作方法的调用。
5.2 案例分析
- 根据上述部门的描述,得出如下的员工体系图:
- 根据员工信息的描述,确定每个员工都有员工编号、姓名、要进行工作。则,把这些共同的属性与功能抽取到父类中(员工类),关于工作的内容由具体的工程师来进行指定。
- 创建JavaEE工程师对象,完成工作方法的调用
6.5.3 示例代码
定义员工类(抽象类)
public abstract class Employee {
private String id; // 员工编号
private String name; // 员工姓名
public String getId() {
returnid;
}
publicvoid setId(String id) {
this.id = id;
}
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
//工作方法(抽象方法)
public abstract void work();
}
定义研发部员工类Developer 继承 员工类Employee
public abstract class Developer extends Employee {
}
定义维护部员工类Maintainer 继承 员工类Employee
public abstract class Maintainer extends Employee {
}
定义JavaEE工程师 继承 研发部员工类,重写工作方法
public class JavaEE extends Developer {
@Override
public void work() {
System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在研发淘宝网站");
}
}
定义Android工程师 继承 研发部员工类,重写工作方法
public class Android extends Developer {
@Override
public void work() {
System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在研发淘宝手机客户端软件");
}
}
定义Network网络维护工程师 继承 维护部员工类,重写工作方法
public class Network extends Maintainer {
@Override
public void work() {
System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在检查网络是否畅通");
}
}
定义Hardware硬件维护工程师 继承 维护部员工类,重写工作方法
public class Hardware extends Maintainer {
@Override
public void work() {
System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在修复打印机");
}
}
在测试类中,创建JavaEE工程师对象,完成工作方法的调用
public class Test {
public static void main(String[] args) {
//创建JavaEE工程师员工对象
JavaEE ee = new JavaEE();
//设置该员工的编号
ee.setId("111111");
//设置该员工的姓名
ee.setName("张伟");
//调用该员工的工作方法
ee.work();
}
}
猜你喜欢
- 2024-09-27 Java多态重载和重写(类方法设计中多态与重载的区别是什么)
- 2024-09-27 什么是多态?Java为什么要用多态?(java中什么是多态性)
- 2024-09-27 别找了!月薪30k的T6大佬整理的Java多态知识点总结,限时收藏
- 2024-09-27 三十一、Java面向对象编程特性-多态
- 2024-09-27 Java多中包括态理解、多态实现、重写、转型和抽象类
- 2024-09-27 Java中的封装、继承和多态,你真的都懂了吗
- 2024-09-27 java基础之多态与向上转型,很用心的一篇笔记
- 2024-09-27 Java多态的介绍-学习日志(java的多态是什么)
- 2024-09-27 Java中的多态(基础语法)(java多态的定义和应用)
- 2024-09-27 Java面试-Java中是如何实现多态的?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)