Java-面向对象编程-三大特性之多态

我们前面已经介绍了面向对象编程的三大特性之二,今天就介绍最后一个特性-多态。
什么叫多态?从字面上理解就是多种形态,即对同一个客体,可以有多种不同的形式。就好像糖一样,有多种口味,你想吃什么口味的就可以吃什么口味。但在程序中,却不是你想要怎样就怎样。更多的是需要怎样去做就怎样去做。来一个算是比较官方的解释:在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数实现的,而在java中,多态的实现主要通过方法重载和方法重写来实现的。实际上, 我们在前面说继承一章的时候,就已经使用过多态这个特性了,只是说还没有将其表现出来而已。请看我们之前定义的几个类:
这里写图片描述
这里写图片描述
你看,这是我们之前设立的两个类,看起来就是一个继承关系而已是吧。实际上,如果我们做以下操作,或许你就发现里面大有乾坤:

操作:新建一个Test类,实现以下代码:
这里写图片描述
如果单纯看右面的代码,我们会觉得说这就是一个普通的继承嘛是吧,但若仔细观察里面的代码(注释处),你会发现,在这个Test类中,执行了这么一个操作:

Animal giraffe =new Giraffe();

而这个操作我们之前真的是前所未见呀,见过最相近的应该也是这个:

Giraffe giraffe =new Giraffe();

那么,这两者有什么区别呢?其实,尽管这两行代码最后所得到的结果是一样的,但是两者却发生了本质的不同。为什么呢?因为前者就是我们要介绍的多态,而后者则是简单的创建一个Giraffe对象的过程而已。嗯,一头雾水,为什么一个Animal类型的变量能够引用Giraffe类型的对象呢?这是因为,Giraffe继承了Animal的一些特性,当我们使用Giraffe的对象时,实际上也可以理解为使用的是Animal对象,但不同的是,我们能使用的Animal对象仅限于Animal仅有的,如果超出了Animal的部分,才当做时Giraffe的自带属性,而如果我们用一个父类型去引用子类对象时,会先访问到子类中重写的父类方法(父类的方法不会再执行),如果子类没有重写父类的方法,才会执行父类中的重写办法。同时,子类中没有继承到父类的部分,是不能被执行的。什么意思呢?代码解释:
修改子类相关方法:不重写Animal类中的eat方法,增加Giraffe的run方法:
这里写图片描述
重新执行Test,结果如下:
这里写图片描述
发现了没?我们在新修改的Giraffe中并没有重写eat方法,所以它执行的是Animal中的方法,而对于已经重写的sleep方法,则执行了子类重写的部分。那么当我们打算执行run方法呢?结果如下:
这里写图片描述
你看出现了错误,意思为run方法没有在Animal类中声明。
所以,这就是我们为什么说:如果我们用一个父类型去引用子类对象时,会先访问到子类中重写的父类方法(父类的方法不会再执行),如果子类没有重写父类的方法,才会执行父类中的重写办法。同时,子类中没有继承到父类的部分,是不能被执行的。而这种调用方式,就是多态的一种状态,叫做向上转型,也是最为容易理解的一种多态方式。
那么,既然有向上转型,自然也有向下转型啦。那么有同学就说了。向上转型是子类转父类,那么向下当然就是父类转子类了,于是巴拉巴拉,把代码修改为:

Giraffe giraffe =(Giraffe) new Animal();

你们觉得这样理解对吗?我们事实说话:执行代码:
这里写图片描述
我们发现,不行,出现了ClassCastException异常,解释是说Animal类不能转化为Giraffe类。说明这个思路是错误的。那么,什么样才是正确的呢?就是:已经转为父类型的子类对象,再转回成子类型,才叫做向下转型:WTF??我要先向上转型为父类,再从父类向下转型为子类???那不是直接和调用子类对象是一样的嘛!看到这里,相信这是大多数人的心声。但实际上,这两者是有区别的,什么区别呢?就是我们可以在向上转型的过程中,对即将进行的向下转型操作进行一定的修改。什么意思呢?请看例子:我们对Animal类和Giraffe进行一定的修改,新增一个属性count,表示吃了多少顿饭。代码如下:
Animal类:

package ClassTest;
//Animal类
public class Animal {
    //新增一个count属性。并设置set和get方法
    private int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
    void eat(){
        System.out.println("动物吃了饭");
    }
    void sleep(){
        System.out.println("动物睡觉");
    }
}

接下来是Giraffe类:

package ClassTest;
//继承自Animal类的Giraffe类
public class Giraffe extends Animal {
    //新增一个count属性
    private int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
    //重写的构造方法
    public Giraffe(int count) {
        super();
        this.count = count;
    }
    //重写的sleep方法
    void sleep(){
        System.out.println("长颈鹿吃了"+count+"顿草");
    }
     //子类增加的方法
    void run(){
        System.out.println("长颈鹿四条腿狂跑");
    }

}

我们先进行第一个Test,代码如下:
这里写图片描述
发现和使用

Giraffe giraffe = new Giraffe(1); 

并没有什么不同,不用怕,接下来就有改变了:

在Test类中新增代码:
这里写图片描述
你看,如此对giraffe的count属性进行修改后,是不是也影响到了giraffe2的count属性呢?这里其实就是借助先向上,再先下这个空隙来实现对数据的修改。当然这只是向下转型的一个方法,实际上向下转型更多是应用在java中的设计模式及泛型之中。而这些知识点会在以后的学习中帮助你更容易去理解多态的作用。
好了,接下来,我们对多态进行一个总结:
1、不管是向上转型,还是向下转型,我们涉及到的都是子类和父类的问题,也就是说多态必须存在继承关系

2、正如上面所说,多态就是多种形态。也就是说,当我们需要实现多态的时候,就需要有父类的方法被子类重写。否则,如果没有重写的方法,就看不出多态的特性,一切按照父类的方法来,还不如不要继承,直接在父类中添加相应的方法,然后在实例化好了

3、不管是向上转型还是向下转型,都有一个共性,就是父类指向子类对象。如果没有这个,也就没有了多态。

而关于多态的表现形式,分为两类:
静态分派,即方法的重载。表现为方法同名不同参,可以存在多个同名的方法,只要参数不一致就行,我们可以根据实际情况调用其中一个,所以称为静态分派。
动态分派,即方法的重写。表现为同名同参不同的执行操作。只能出现一个方法,而方法内部的操作时动态更改的。每次能且仅能调用一个方法。因为它并没有被人选择地特性,所以为动态。

好了,至此,面向对象的三大特性已经介绍完毕。不过需要注意的是。在这介绍的其实是不全面的一部分,可以说只算得上是入门知识。真正要对三大特性有一个了解,还需要不断去编码,学习,越往深处学,才越能懂得。否则,要是直接巴拉巴拉说得相对全面了,你也会一脸懵逼。毕竟这三大特性是贯穿整个java的,很多技术上的理解需要其他知识的相互配合去支持,不然就根本不知道说的是什么了。好了,让我们一起往java的深处行走吧,下一章是比较烧脑的一章:“java-面向对象编程-内部类”,敬请期待。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页