一篇文章带你了解Java基础-多态

目录
  • Java基础知识(多态)
    • 多态
      • 多态的定义和存在的必要条件
      • 多态的案例
      • 多态的弊端
      • 引用类型转换
  • 总结

Java基础知识(多态)

多态

多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

多态的定义和存在的必要条件

多态的定义:

多态是指同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作。

就举动物类的例子吧,cat和dog都是属于动物这一类,而动物呢,都有一个共同的行为就是吃吧,而不同的动物所吃的食物都大不相同吧!

猫呢,它喜欢吃鱼!

而对于狗呢,它就比较喜欢啃骨头!

所以多态就是对于吃这一行为来说,每种动物对吃这一行为所表现的行为都不尽相同。

多态存在的三个必要条件

1.继承或者实现

在多态中必须存在有继承或者实现关系的子类和父类。

2.方法的重写

子类对父类中某些方法进行重新定义(重写),在调用这些方法时就会调用子类的方法。

3.基类引用指向派生类对象,即父类引用指向子类对象

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

多态的格式:

父类类型 变量名 = new 子类类型();
变量名.方法名();

多态格式可以充分体现了同一个接口,使用不同的实例而执行不同操作。

接下来我们具体来进行案例体会体会吧!

多态的案例

多态我们首先要知道的一点:

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。如果子类没有重写该方法,就会调用父类的该方法。

总结起来就是:编译看左边,运行看右边。

首先我们先定义一个父类动物类,动物有吃的行为!

接着定义一个猫类和狗类去继承动物类,重写里面的吃行为!

具体代码如下:

定义动物父类:

package com.nz.pojo;
/**
 * 先定义一个父类 --> 动物类
 * 动物都有一个吃的行为属性
 */
public class Animal {
    public void eat() {
        System.out.println("动物它们都会吃东西!!!");
    }
}

定义猫咪子类:

package com.nz.pojo;
/**
 * 定义猫类继承动物类,
 * 随之重写里面的吃行为,因为猫也有吃的行为,但是猫喜欢吃罐头
 */
public class Cat extends Animal{
    public void eat() {
        System.out.println("小喵咪都喜欢吃鱼罐头!");
    }
}

定义小狗子类:

package com.nz.pojo;
/**
 * 定义狗类继承动物类,
 * 随之重写里面的吃行为,因为狗也有吃的行为,但是狗喜欢啃骨头
 */
public class Dog extends Animal{
    public void eat() {
        System.out.println("小狗狗都爱啃骨头!");
    }
}

定义测试类,测试多态的形式:

package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
 * 测试多态的形式
 */
public class Demo {
    public static void main(String[] args) {
        // 多态形式,创建猫类对象
        Animal animal = new Cat();
        // 调用的是Cat的 eat
        animal.eat();
        // 多态形式,创建狗类对象
        Animal animal2 = new Dog();
        // 调用的是Dog的eat
        animal2.eat();
    }
}

得到的结果:

小喵咪都喜欢吃鱼罐头!
小狗狗都爱啃骨头!

类的大致结构:

可以看出我们可以使用多态的属性得到不同的动物的一个吃的行为属性!

多态的好处

提高了代码的拓展性,使用父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用。

具体我们来看看吧:

继续使用上述的动物类、猫类、狗类吧。

定义测试类:

package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
 * 测试多态的好处
 */
public class Demo2 {
    public static void main(String[] args) {
        // 创建猫和狗对象
        Cat cat = new Cat();
        Dog dog = new Dog();
        // 调用catEat
        catEat(cat);
        // 调用dogEat
        dogEat(dog);
		/*
        多态的好处:
            以上各个动物的吃的方法, 我们都可以使用animalEat(Animal a)方法来代替。
            并且执行效果都一样, 所以我们可以使用animalEat直接替代了不同动物的吃方法。
        */
        animalEat(cat);
        animalEat(dog);
    }
    /*
        定义几个不同吃的方法,看看具体调用后的结果是什么吧!
     */
    public static void catEat (Cat cat){
        cat.eat();
    }
    public static void dogEat (Dog dog){
        dog.eat();
    }
    public static void animalEat (Animal animal){
        animal.eat();
    }
}

执行结果:

小喵咪都喜欢吃鱼罐头!
小狗狗都爱啃骨头!
小喵咪都喜欢吃鱼罐头!
小狗狗都爱啃骨头!

可以看出,由于多态的特性,我们的animalEat()方法传入的Animal类型参数,并且它是我们的Cat和Dog的父类类型,父类类型接收子类对象,所以我们可以将Cat对象和Dog对象,传递给animalEat()方法。

所以我们可以完全使用animalEat()方法来替代catEat()方法和dogEat()方法,达到同样的效果!以至于我们可以不必再单独写xxxEat()方法来传入指定的动物参数了,从而实现了实现类的自动切换。

所以多态的好处体现在:可以使我们的程序编写的更简单,并有良好的扩展性。

多态的弊端

从上面的多态的好处,可以看到我们可以使用父类的参数代替了某个子类的参数,从而达到程序的扩展!

但是对于某个子类有些独有的功能方法时,此时我们的多态的写法就无法访问子类独有功能了。

具体来瞧瞧?

代码如下:

重新定义下猫的子类:

package com.nz.pojo;
/**
 * 定义猫类继承动物类,
 * 随之重写里面的吃行为,因为猫也有吃的行为,但是猫喜欢吃罐头
 */
public class Cat extends Animal{
    public void eat() {
        System.out.println("小喵咪都喜欢吃鱼罐头!");
    }
    /**
     * 增加一哥猫咪特有的玩球方法()
     */
    public void playBall() {
        System.out.println("小喵咪都喜欢小球!");
    }
}

定义测试类:

package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
/**
 * 测试多态的弊端!
 */
public class Demo3 {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat();
        animal.playBall();//编译报错,编译看左边,Animal没有这个方法
    }
}

可以看到动物类和猫类有个共同的eat吃方法,但是呢,猫咪多了个玩球球的方法。而对于动物对象来说,它本身动物类没有玩球球的方法,所以它的编译就直接没有通过了!

那有什么方法解决呢?且看下一章节吧!

引用类型转换

1. 引用类型转换是什么,为什么需要它?

从上面的多态的弊端的案例中,我们可以看到,我们使用动物对象时无法直接访问到猫类中的玩球球方法,这也就是我们之前说的编译看左边,运行看右边。

而在我们使用多态方式调用方法时,首先检查会左边的父类中是否有该方法,如果没有,则编译错误。也就代表着,父类无法调用子类独有的方法。、

所以说,如果编译都错误,更别说运行了。这也是多态给我们带来的一点小困扰,而我们如果想要调用子类特有的方法,必须做向下转型。

2. 向上转型(自动转换)

对于向下转型,我们先来讲解下向上转型的概念吧。

向上转型:

多态本身是子类向父类向上转换(自动转换)的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。

对于父类和子类的关系来说,具体来看图说话:

父类相对与子类来说是大范围的类型,Animal是动物类,是父类。而Cat是猫咪类,是子类。

所以对于父类Animal来说,它的范围是比较大的,它包含一切动物,包括猫咪类和小狗类。

所以对于子类类型这种范围小的,我们可以直接自动转型给父类类型的变量。

使用格式:

父类类型 变量名 = new 子类类型();
如:Animal animal = new Cat();
相当于有:
Animal animal = (Animal) new Cat();

相当于自动帮我们了一个隐形的转换为动物类的一个过程,因为动物本身就包含了猫咪。

3. 向下转型(强制转换)

向上转型可以知道它是子类自动转换为父类的一个过程,所以我们现在再来看看向下转型的定义:

向下转型:

向下转型就是由父类向子类向下转换的过程,这个过程是强制的。一个需要将父类对象转为子类对象,可以使用强制类型转换的格式,这便是向下转型。

为什么这种就必须自己强制加上一个类型转换过程呢?

对于父类和子类的关系来说,我们接着看图说话:

对于猫咪类的话,它在动物类中只是其中的一部分吧,而对于动物类来说,它有许多其他子类动物如狗,牛,猪等等。

所以对于动物父类想要向下转型的时候, 它此时不知道指向那个子类,因为不确定呀,所以就必须自己加上强制的类型转换的一个过程。

使用格式:

子类类型 变量名 = (子类类型) 父类变量名;
如:
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.playBall();// 此时我们就可以使用猫咪的特有方法啦

所以对于多态的弊端,无法使用子类特有的参数,我们也解决啦,可以通过向下转型的方法,从而将类型强制转换为某个子类对象后,再去调用子类的特有方法!

4. 向下转型的问题

虽然我们可以使用向下转型使得我们可以使用子类的独有方法,但是转型的过程中,一不小心就会遇到这样的问题了,来,我们来看看下面的代码:

public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal a = new Cat();
        a.eat();               // 调用的是 Cat 的 eat
        // 向下转型
        Dog d = (Dog)a;
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }
}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

5. 转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

定义狗类中额外的独有遛狗方法:

package com.nz.pojo;
/**
 * 定义狗类继承动物类,
 * 随之重写里面的吃行为,因为狗也有吃的行为,但是狗喜欢啃骨头
 */
public class Dog extends Animal{
    public void eat() {
        System.out.println("小狗狗都爱啃骨头!");
    }
    public void walk() {
        System.out.println("小狗在被我溜着!");
    }
}

定义测试类

package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
 * 测试多态的向下转型的问题
 */
public class Demo4 {
    public static void main(String[] args) {
        // 向上转型的过程
        Animal animal = new Cat();
        // 调用了猫咪的吃方法
        animal.eat();
        // 向下转型
        Dog dog = (Dog) animal;
        dog.walk(); // 调用的是 Dog 的 walk 可以通过,但是会运行报错
    }
}

得到结果:

小喵咪都喜欢吃鱼罐头!
Exception in thread "main" java.lang.ClassCastException: com.nz.pojo.Cat cannot be cast to com.nz.pojo.Dog
at com.nz.Demo4.main(Demo4.java:20)

我们可以看到,虽然我们的代码通过编译,但是终究在运行时,还是出错了,抛出了 ClassCastException 类型转换的异常。

其实我们可以知道,我们在上面的时候,创建了Cat类型对象,而在向下转型时,将其强行转换为了Dog类型,所以程序在运行时,就会抛出类型转换的异常!

那我们如何可以避免这种异常发生呢?且看下一节分析!

6. instanceof关键字

Java为我们提供一个关键字instanceof ,它可以帮助我们避免了ClassCastException 类型转换异常的发生。

那如何做呢?

格式:

变量名 instanceof 数据类型

解释:

  • 如果变量属于该数据类型或者其子类类型,返回true。
  • 如果变量不属于该数据类型或者其子类类型,返回false。

代码实现:

package com.nz;
import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;
/**
 * 使用instanceof解决类型转换异常!
 */
public class Demo5 {
    public static void main(String[] args) {
        // 向上转型的过程
        Animal animal = new Cat();
        // 调用了猫咪的吃方法
        animal.eat();
        // 向下转型
        if (animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.playBall();        // 调用的是 Cat 的 playBall
        } else if (animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.walk();       // 调用的是 Dog 的 walk
        }
    }
}

结果:

小喵咪都喜欢吃鱼罐头!
小喵咪都喜欢小球!

可以发现,它可以帮助我们在做类型转换前,判断该类型是否属于该类型或者子类类型,如果是,我们就可以强转啦!

总结

相信各位看官都对Java中的特性之一多态的知识和使用有了一定了解,等待下一次更多Java基础的学习吧!

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 详细理解JAVA面向对象的封装,继承,多态,抽象

    目录 类和对象的使用(面向对象思想落地的实现): 子类对象实例化的全过程 1.从结果上看:(继承性) 2.从过程上来看: 1.封装性 2.继承性 继承性的好处: 3.多态性 虚拟方法调用 4.抽象性 1.抽象类的特点: 2.天生的父类:抽象类 3.抽象方法 总结 创建类的对象 = 类的实例化 = 实例化类 类和对象的使用(面向对象思想落地的实现): 1.创建类,设计类的成员 2.创建类的对象 3.通过"对象.属性"或"对象.方法"调用对象的结构 如果创建了一个类的多

  • Java抽象类、继承及多态和适配器的实现代码

    Java继承 方法重写是Java语言多态的特性,必须满足以下条件 在子类中,方法名称与父类方法名称完全相同 方法的参数个数和类型完全相同,返回类型完全相同 方法的访问修饰符访问级别不低于父类同名方法的访问级别 在方法上添加@override注释,如果报错说明不是重写 方法重写限制 final修饰的父类方法在子类中不能被重写 static修饰的父类方法在子类中不能被重写,只能覆盖 super关键字 super关键字和this类似,super修饰的是父类的对象,如super();调用的是父类的默认无

  • Java初学者入门之继承和多态

    前言 首先我们如果要使用Java中存在的包,可以程序中使用import语句导入包.包说通俗点就是一个 文件夹,为了方便管理. 在程序中声明包的语法: package <包名> 注意:声明一个包的语句必须写在类中的第一行. 在程序中导入包的格式: import <包名>.<类名> 重点来了,继承! 继承是面向对象程序设计的一个重要特征,它是通过继承原有类派生出的子类进而构造出更为复杂的子类.子类既有新定义的行为特征,又继承了原有类的行为特征.我们可以这样进一步认为:父类更

  • Java初学之继承与多态

    目录 在程序中声明包的语法: Java继承语法格式: 什么是重写呢?: 多态应用: 引用变量的强制类型转换 instanceof 运算符 总结 首先我们如果要使用Java中存在的包,可以程序中使用import语句导入包.包说通俗点就是一个 文件夹,为了方便管理. 在程序中声明包的语法: package <包名> 注意:声明一个包的语句必须写在类中的第一行. 在程序中导入包的格式: import <包名>.<类名> 重点来了,继承! 继承是面向对象程序设计的一个重要特征,

  • 新手初学Java继承、封装与多态

    目录 面向对象的三大核心特性 封装 继承 单继承 继承的优缺点 super关键字 super调用父类构造方法 super访问父类成员 super和this的区别 多态 instanceof关键字 方法重载 方法重写 抽象类 接口 定义接口 实现接口 总结 面向对象的三大核心特性 面向对象开发模式更有利于人们开拓思维,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率.面向对象程序设计有以下优点. 可重用性:代码重复使用,减少代码量,提高开发效率.下面介绍的面向对象的三大核心特性(

  • 一篇文章带你了解Java基础-多态

    目录 Java基础知识(多态) 多态 多态的定义和存在的必要条件 多态的案例 多态的弊端 引用类型转换 总结 Java基础知识(多态) 多态 多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定. 因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用

  • 一篇文章带你了解Java基础-接口

    目录 Java基础知识(接口) 接口 接口的定义 接口和抽象的区别 接口的格式 接口中的主要成分 接口的案例 接口与接口的关系 JDK 8之后的接口新增方法 总结 Java基础知识(接口) 接口 Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能). 接口的定义 接口: 在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明.一个类通过继承接口

  • 一篇文章带你了解Java基础-抽象

    目录 Java基础知识(抽象) 抽象 抽象定义 abstract的使用 定义抽象类 抽象类的一些注意点 总结 Java基础知识(抽象) 抽象 抽象是从众多的事物中抽取出共同的.本质性的特征,而舍弃其非本质的特征的过程.具体地说,抽象就是人们在实践的基础上,对于丰富的感性材料通过去粗取精.去伪存真.由此及彼.由表及里的加工制作,形成概念.判断.推理等思维形式,以反映事物的本质和规律的方法. 抽象定义 在继承过程中,我们知道父类的方法可以子类进行重写,而每个子类各自的实现都不尽相同.就好比动物都有吃

  • 一篇文章带你了解Java Spring基础与IOC

    目录 About Spring About IOC Hello Spring Hello.java Beans.xml Test.java IOC创建对象的几种方式 Spring import settings Dependency Injection 1.构造器注入 2.set注入 3.拓展注入 P-namespcae&C-namespace Bean scopes singleton prototype Bean的自动装配 byName autowire byType autowire 小结

  • 一篇文章带你了解Java中ThreadPool线程池

    目录 ThreadPool 线程池的优势 线程池的特点 1 线程池的方法 (1) newFixedThreadPool (2) newSingleThreadExecutor (3) newScheduledThreadPool (4) newCachedThreadPool 2 线程池底层原理 3 线程池策略及分析 拒绝策略 如何设置maximumPoolSize大小 ThreadPool 线程池的优势 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些

  • 一篇文章带你复习java知识点

    JDK JRE JVM JDK: Java标准开发包,它提供了编译.运⾏Java程序所需的各种⼯具和资源,包括Java编译器.Java运⾏时环境,以及常⽤的Java类库等. JRE: Java运⾏环境,⽤于解释执⾏Java的字节码⽂件. JVM Java虚拟机,是JRE的⼀部分.负责解释执⾏字节码⽂件,是可运⾏java字节码⽂件的虚拟计算机 区别联系:(问答题会考可能) JDK包含JRE,JDK 和 JRE 中都包含 JVM.JDK出了包含jre还包含⼀些常⽤开发⼯具和基础类库 JDK ⽤于开发

  • 一篇文章带你入门Java Script

    目录 概述 特点 和Java的区别 弱类型语言 强类型语言 书写位置 数组 函数 JS中的自定义对象(扩展内容) Object形式的自定义对象 JS中的事件 常用的事件: 动态注册基本步骤: DOM模型 总结 概述 JavaScript是目前web开发中不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,需要通过浏览器来解析执行JavaScript代码. JS组成部分: 组成部分 作用 ECMA Script 构成了JS核心的语法基础 BOM Browser Object Model 浏览

  • 一篇文章带你入门java集合

    目录 一.简介 1.java集合框架图 2.集合框架体系 3.Set和List的区别 二.ArrayList 1.定义 2.用实例了解ArrayList 三.LinkedList 1.语法 2.示例 四.HashSet 1.定义 2.语法 3.示例 五.HashMap 1.定义 2.语法 3.示例 Java HashMap 方法 六.Iterator(迭代器) 1.定义 2.示例 七.List和数组互转 总结 一.简介 1.java集合框架图 从上面的集合框架图可以看到,Java 集合框架主要包

  • 一篇文章带你入门java面向对象

    目录 一.继承 示例: 二.重载 三.接口 1.接口与类相似点: 2.接口与类的区别: 3.语法 四.枚举 1.定义 2.迭代枚举元素 3.在 switch 中使用枚举类 总结 一.继承 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为 本章就以人.学生.老师作为例子.学生和老师都继承人这个对象,都有人的特征和行为,人就是父类,老师和学生就是子类 示例: 人类: package com.zhouzy.base.t7;

  • 一篇文章带你了解Java方法的使用

    目录 方法的基本用法 方法定义 基本语法格式: 为什么方法一般用public static修饰? 代码示例: 注意事项: 方法调用的调试过程 IDEA 的调试过程: 开始调试,点击"甲壳虫" 注意事项: 暂停调试 方法的重复调用:

随机推荐