1、多态介绍

面向对象三大特征:封装、继承、多态。多态是Java面向对象最核心,最难以理解的内容。从一定角度来看,封装和继承几乎都是为多态而准备的。

多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

比如有一种动物,藏在某个看不见得地方,你知道它是一种动物,但是不知道具体是哪种动物,只有等它发出叫声才能辨别,是猫——喵喵喵,是狗——旺旺旺,你一听就知道这是什么动物,对于不同的动物会有不同的结果,可以理解为多态。

Java多态也可以用一句话表示:父类的引用指向子类的对象。

多态存在的三个必要条件

1、继承:在多态中必须存在有继承关系的子类和父类。

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

3、向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

2、多态的实现

开头说了这么多Java多态的基本概念,那么到底用Java代码怎么体现出来呢?下面我们直接看代码。

public class Animal {

    public void eat(){
        System.out.println("动物吃东西");
    }

    public void skill(){
        System.out.println("动物有本领");
    }
}

class Dog extends Animal{

    public void eat() {
        System.out.println("狗吃骨头");
    }

    public void skill(){
        System.out.println("狗会看家");
    }
}

class Cat extends Animal{
    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void skill(){
        System.out.println("猫会抓老鼠");
    }
}

//测试类
public class AnimalTest {

    public static void main(String[] args) {
        Animal dog=new Dog();
        dog.eat();
        dog.skill();
        Animal cat=new Cat();
        cat.eat();
        cat.skill();
    }
}

运行结果:

从上面的测试类来看,我们都是创建不同子类的对象,相同的父类引用,却表现出它们不同的特征,这就是体现了Java的多态性。

如果你认为这样还是不能体现多态的好处,我们在AnimalTest类中添加一个show()方法,来体会Java多态的好处。

如果Java中没有多态的特性会是什么样的,下面我们来看一下。

public class AnimalTest {

    public static void main(String[] args) {

        AnimalTest animalTest=new AnimalTest();
        animalTest.show(new Animal());
        animalTest.show(new Dog());
        animalTest.show(new Cat());
    }

    public void show(Animal animal){
        animal.eat();
        animal.skill();
    }

    public void show(Dog dog){
        dog.eat();
        dog.skill();
    }

    public void show(Cat cat){
        cat.eat();
        cat.skill();
    }
}

可以发现在show()方法中的形参都传入了对象,而且重载3个相同的show()方法,如果我们有十个百个千个类需要传入方法,那么岂不是要重载上千个方法,可见这样代码的冗余非常的大,非常不利于代码的维护。

而如果有多态的话,只需写一个show()方法即可。

public class AnimalTest {

    public static void main(String[] args) {

        AnimalTest animalTest=new AnimalTest();
        animalTest.show(new Animal());
        animalTest.show(new Dog());
        animalTest.show(new Cat());
    }

    public void show(Animal animal){
        animal.eat();
        animal.skill();
    }
}

从这里可以看出来多态的优点:

1、减少重复代码,使代码变得简洁(由继承保证)。

2、提高了代码的扩展性(由多态保证)。

但是也有缺点:子类单独定义的方法会丢失。后面的向上转型会介绍到。

多态其实是一种虚拟方法调用。在编译期间,只能调用父类中声明的方法,但是在运行期间,实际执行的是子类重写父类的方法。

总结为一句话:编译看左边,运行看右边(可能看到这句话会有点头晕,但是理解下面向上转型的概念就应该能够理解这句话了)。

3、向上转型

子类引用的对象转换为父类类型称为向上转型。通俗地说就是是将子类对象转为父类对象。此处父类对象也可以是接口。

我们用前面多态的例子举例,只是在子类中添加了它们自己的方法,父类中没有定义,如下。

public class Animal {

    public void eat(){
        System.out.println("动物吃东西");
    }

    public void skill(){
        System.out.println("动物有本领");
    }
}

class Dog extends Animal{

    public void eat() {
        System.out.println("狗吃骨头");
    }

    public void skill(){
        System.out.println("狗会看家");
    }
    //新添加的方法
    public void run(){
        System.out.println("狗跑得快");
    }
}

class Cat extends Animal{
    public void eat() {
        System.out.println("猫吃鱼");
    }

    public void skill(){
        System.out.println("猫会抓老鼠");
    }
    //新添加的方法
    public void life(){
        System.out.println("猫有九条命");
    }
}

//测试类
public class AnimalTest {
    public static void main(String[] args) {
        Animal dog=new Dog();//向上转型成Animal
        dog.eat();
        dog.skill();
        //dog.run();//Cannot resolve method 'run()'
        Animal cat = new Cat();//向上转型成Animal
        cat.eat();
        cat.skill();
        //cat.life();//Cannot resolve method 'life()'
    }
}

这里就产生了向上转型,Animal dog= new Dog();Animal cat= new Cat();将子类对象Dog和Cat转化为父类对象Animal。这个时候Animal这个引用调用的都是子类方法。再去调用子类单独的方法就会报错。如果非要调用也不是说不可以,那就要强转了。既然现在已经是父类了,那就强转为子类呗。

((Dog) dog).run();

((Cat) cat).life();

这样也可以调用。但是千万要注意,不能这样转,子类引用不能指向父类对象,Dog dog=(Dog)newAnimal();Cat cat = (Cat)new Animal();这样是绝对不行的。就好像儿子可以生出爸爸一样,这样不和常理。

如果在向上转型时,子类并没有重写父类的方法,那么调用的就是父类中的方法。

到此为止,也可以证明前面说的一个结论:向上转型会使子类单独定义的方法会丢失。

4、向下转型

与向上转型相对应的就是向下转型了。向下转型是把父类对象转为子类对象。这里我们就会想到,即然子类向上转型为了父类,而子类又继承了父类的属性和方法,为什么还要将父类转型为子类。是因为对象的多态性只适用于方法,而不适用于属性。所以当我们在使用多态的时候,就不能调用子类中的属性和特有的方法了,所以需要向下转型。(内存中实际上是加载了子类所特有的属性和方法,但是由于变量声明的是父类类型,导致在编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用)

我们还是使用向上转型那里的代码为例(Anima、Dog、Cat类):

public class AnimalTest {
    public static void main(String[] args) {
        Animal dog=new Dog();//Dog向上转型成Animal
        Dog dog1= (Dog) dog;//向下转型为Dog
        dog1.eat();
        dog1.skill();

        Cat cat=(Cat) dog;//java.lang.ClassCastException: com.thr.java2.Dog cannot be cast to com.thr.java2.Cat
        cat.eat();
        cat.skill();
    }
}

运行结果:

我们可以发现向下转型为Dog没有报错,但是转型为Cat却报错了,这个倒不难理解,因为开始向上转型本来是Dog,然后再变回Dog,总不能Dog变成Cat吧。所以会报类型转换错误。

向下转型注意事项

  1. 向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型)
  2. 向下转型只能转型为本类对象(猫是不能变成狗的)。

向下转型我们一般会使用 instanceof 关键字来判断:

使用方法:a instanceof A:判断对象a是否为对象A的实例,如果是,返回true,如果不是,则返回false。

public class AnimalTest {
    public static void main(String[] args) {
        AnimalTest test=new AnimalTest();
        test.show(new Animal());
        test.show(new Dog());
        test.show(new Cat());
    }

    public void show(Animal animal){
        if (animal instanceof Dog){
            Dog dog= (Dog) animal;
            dog.eat();
            dog.skill();
            dog.run();
        }
    }
}

运行结果:

我们可以发现测试方法调用三次show方法,分别传入了Animal、Dog、Cat对象,由于show()方法只判断了Dog是否是该对象,所以Dog返回了true,输出了Dog的信息,而其他的返回了false,则没有输出然后信息。

5、经典案例

Java的多态和转型都了解以后,现在趁热打铁,来点网上多态非常经典的例题:

来源:https://blog.csdn.net/Jian_Yun_Rui/article/details/52937791

    public class A {
        public String show(D obj) {
            return ("A and D");
        }  

        public String show(A obj) {
            return ("A and A");
        }   

    }  

    public class B extends A{
        public String show(B obj){
            return ("B and B");
        }  

        public String show(A obj){
            return ("B and A");
        }
    }  

    public class C extends B{  

    }  

    public 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("4--" + a2.show(b));
            System.out.println("5--" + a2.show(c));
            System.out.println("6--" + a2.show(d));
            System.out.println("7--" + b.show(b));
            System.out.println("8--" + b.show(c));
            System.out.println("9--" + b.show(d));
        }
    }  

运行的结果:

前面3个强行发现还能得到答案,但是从第4个之后就有点头晕。

我们来慢慢分析第4个:首先是子类B类向上转型为父类A,而子类B有重写了父类A中的show(A obj)方法,所以a2变量能调用的只有父类A类中的show(D obj)和子类B中的show(A obj),而B是继承自A类的,D继承自B类,所以不可能调用show(D obj)方法,所以结果是4--B and A;剩下的依次类推。

当父类对象变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找。但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

6、总结

通过上面对多态的学习,可以小结一下概念:

1、Java多态也可以用一句话表示:父类的引用指向子类的对象。

2、运行时多态的前提:继承,重写,向上转型。

3、多态能够减少重复代码,使代码变得简洁;提高了代码的扩展性。

4、多态其实是一种虚拟方法调用。归结为一句话:编译看左边,运行看右边。

5、向上转型就是是将子类对象转为父类对象

6、继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

夯实Java基础(四)——面向对象之多态的更多相关文章

  1. 夯实Java基础系列1:Java面向对象三大特性(基础篇)

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

  2. 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

    目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...

  3. 夯实Java基础系列目录

    自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...

  4. 夯实Java基础系列10:深入理解Java中的异常体系

    目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...

  5. Java工程师学习指南第1部分:夯实Java基础系列

    点击关注上方"Java技术江湖",设为"置顶或星标",第一时间送达技术干货. 本文整理了微信公众号[Java技术江湖]发表和转载过的Java优质文章,想看到更多 ...

  6. Java基础-初识面向对象编程(Object-Oriented-Programming)

    Java基础-初识面向对象编程(Object-Oriented-Programming) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Java是一门面向对象的程序设计语言.那么什 ...

  7. 夯实Java基础(十一)——内部类

    1.内部类的概念 内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜 ...

  8. 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

    目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...

  9. 夯实Java基础系列5:Java文件和Java包结构

    目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...

  10. 夯实Java基础系列7:一文读懂Java 代码块和执行顺序

    目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块 ...

随机推荐

  1. MyBatis从入门到精通(六):MyBatis动态Sql之if标签的用法

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用if标签生成动 ...

  2. 微服务-springboot-rabbitmq:实现延时队列

    延时队列应用于什么场景 延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费.那么,为什么需要延迟消费呢?我们来看以下的场景 网上商城下订单后30分钟后没有完成支 ...

  3. java-基础语法01

    一.变量 1. 何为变量?:在数学中变量就是一个不确定的量,随时都会改变,在java中变量也是这样,只不过它是内存中装载数据的小盒子,你只能用它来存数据和取数据. 2. 变量的基本类型(四类八种),见 ...

  4. 个人亲测,在win10系统下安装多实例mysql8.0详细教程

    由于公司的新项目需要导入sql脚本,需要更高版本的mysql数据库,原来的数据库我也不想删除和升级,因此安装了第二个mysql8的实例,废话不多说,步骤如下: 1.下载mysqlGPL版本,我下载的版 ...

  5. 使用开源框架Sqlsugar结合mysql开发一个小demo

    一.Sqlsugar简介 1.性能上有很大优势 sqlsugar是性能最好的ORM之一,具有超越Dapper的性能 ,走的是EMIT够构中间语言动态编译到程序集,完成高性能的实体绑定,达到原生水平. ...

  6. Java学习笔记之---面向对象

    Java学习笔记之---面向对象 (一)封装 (1)封装的优点 良好的封装能够减少耦合. 类内部的结构可以自由修改. 可以对成员变量进行更精确的控制. 隐藏信息,实现细节. (2)实现封装的步骤 1. ...

  7. 【Spring容器】项目启动后初始化数据的两种实践方案

    早期业务紧急,没有过多的在意项目的运行效率,现在回过头看走查代码,发现后端项目(Spring MVC+MyBatis)在启动过程中多次解析mybatis的xml配置文件及初始化数据,对开发阶段开发人员 ...

  8. dubbo webservice 区别

    DUBBO中可以设置采用webservice方式,进行数据交互. 随着交互系统的增多,这种方式对系统的侵入性越来越大,关系更为错综复杂,很容易出错. 较适用与外围系统通信,若是内部系统间则会出现以上较 ...

  9. Java操作文件

    import java.io.File; import java.io.IOException; import java.nio.file.*; import java.nio.file.attrib ...

  10. CSDN怎么一键转载别人的博客

    在参考"如何快速转载CSDN中的博客"后,由于自己不懂html以及markdown相关知识,所以花了一些时间来弄明白怎么转载博客,以下为转载CSDN博客步骤和一些知识小笔记. 参考 ...