1、多态
1.1 简单了解多态
今天的内容有一点点抽象,但是不难,内容也不多。
不知道各位小伙伴在看到多态这两个字的时候会有什么样的想法,如果我从字面上的意思来理解的话,无非就是一个事物有多种表示形态,就好比你打游戏,一个角色能切换多种形态,那如果放在一门面向对象的编程语言中,如何理解多态呢?
简单来说,也就是有多种形态, 当不同的对象去完成一件事时,会产生不同的状态,就好比,猫跟狗都要吃饭,而猫吃饭的时候吃的是猫粮,狗吃饭的时候吃的是狗粮,他们都在做吃饭这个行为的时候,但是吃的东西是不同的,就可以理解成多态。
简而言之:同一种行为,发生在不同对象的身上,他们的产生的结果也就不同。
1.2 多态的实现需要具备哪些条件?
这里有几点想实现多态的硬性要求:
- 必须在继承的体系下
- 子类必须要对父类的方法进行重写
- 是通过父类的引用去调用子类重写父类的方法
最后一条如何理解呢?也就是说,我用父类引用了一个子类的对象,也就导致代码在运行的时候,当父类引用子类对象的不同,也就会调用对应子类中重写的方法。
在正式开始学习多态之前,我们需要先了解重写和向上和向下转型:
2、方法重写
2.1 重写的概念
重写(override) :如果从字面通俗的理解,虽然我继承了你的类,但我感觉你里面的一个成员放方法不满足我类的需求,我就要进行你类中成员方法的重写,既然是重写,方法名(有例外),返回值,形参都不能改变,我只需要对方法体进行改变。重写的好处是什么呢?在于子类可以根据需要,定义特定自己的行为,也就是说子类能够根据需要实现父类的方法。
那这里我们就来简单见一见重写:
public class Animal { private String name; private int age; public void eat() { System.out.println(this.name + "正在吃"); } } class Dog extends Animal { //这里是方法的重写 @Override public void eat() { System.out.println(super.getName() + "正在吃狗粮"); } }
由上代码可以看到,Animal 类中有一个 eat 方法,但是在 Dog 继承了 Animal 之后,重写了一个 eat 方法,你可以理解成,我认为狗不应该吃饭,不够准确,所以我要重写成吃狗粮。
2.2 方法重写的规则
- 子类在重写父类方法的时候,一般必须于与父类的方法原型一致:返回值类型(可以不同,但必须具有父子关系),参数列表也要完全一致。
- 访问权限不能比父类中被重写方法的访问权限更低,比如:父类的成员方法被public修饰, 那么子类中重写该方法的权限必须大于等于public
- 父类被 static,final,private修饰的方法都不能被重写
- 重写方法的时候,可以使用 @Override 注解来显式指定,这个注解能帮我们进行一些合法的校验。
2.3 重写和重载的区别
这也算是面试中会被问到一道题,请简单说一下重写的重载的区别:
- 参数列表:重写的参数列表一定不能修改,重载的参数列表必须求改
- 返回类型:重写的返回类型必须与父类一致(除非构成父子关系),重载的返回类型不做要求
- 访问限定符:重写访问限定符必须大于等于父类,重载的访问限定符不做要求
总而言之:重写是子类与父类的一种多态性表现,重载则是一个类的多态性的表现。
2.4 为什么需要重写呢?
重写的设计原则其实是,对于已经投入使用的类,我们尽量不要进行修改,最好是重新定义一个类,来重复利用其中共性的内容,增加或者改动新的内容,举一个很简单的例子,在这个通讯飞速发展的时代,人人都离不开手机,而手机的更新迭代是非常快的,系统更新也是很快,假设我有一个新品,他支持一个新的特性,要在原有特性上做修改,那我们首先得保证之前老款手机得不受影响,而新手机也得在原有的特性上进行优化和增加功能,这里就可以进行重写了。你可以想一想,如果在原有的基础上更新,那些老款不支持新的功能怎么办?
静态绑定:也称之为早期绑定,就是在编译的时候,已经根据用户所传递的实参类型确定了调用哪个方法,典型代表就是方法重载
动态绑定:也称之为后期绑定,就是在编译的时候,还不确定调用哪个方法,需要等到程序运行的时候,才能具体确认调用哪个类的方法,典型代表就是多态(往后看,你就明白了)
3、向上转型和向下转型
3.1 向上转型
这个其实前面我们有简单提到过,但没有用代码实现和深入研究,那么在这里我们用代码来见识下什么是向上转型:
向上转型,简而言之就是创建一个子类的对象,把他当作父类的对象来使用,就拿我们上面的 Animal 和 Dog 来举例:
Animal animal = new Dog();
这里 Animal 是 Dog 的父类,而我我们创建了一个子类对象,却把他用父类来引用着,也就是把他当作成父类的对象来使用了,这就是向上转型,如何画图理解呢:
在简单一点,animal 是父类类型,但是可以引用一个子类对象,因为是从小范围向大范围的转换,就好比说,猫跟狗都是动物,因此将猫和狗子类对象转化为父类引用是合理的,大范围是可以囊括小范围的,是安全的!
3.2 结合向上转型,正式学习多态
前面讲一大堆,其实都是在为多态而作铺垫,现在我们来利用上我们前面学习的重写和向上转型,实现一个简单的多态代码:
public class Animal { private String name; private int age; public Animal(){} //提供无参构造方法 //带参数的构造方法 public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(this.name + "正在吃"); } public String getName() { return name; } } class Dog extends Animal { public Dog(){} //带参数的构造方法 public Dog(String name, int age) { super(name, age); } //这里是方法的重写 @Override public void eat() { System.out.println(super.getName() + "正在吃狗粮"); } } class Cat extends Animal { public Cat(){} //带参数的构造方法 public Cat(String name, int age) { super(name, age); } //这里是方法的重写 @Override public void eat() { System.out.println(super.getName() + "正在吃猫粮"); } }
我们细读上面代码,其实有把我们前面的知识串联起来,比如使用了 super 调用了父类的方法来获取被 private 修饰的 name,同时 Dog 和 Cat 类都继承了 Animal ,也实现了父类方法的重写,以及我们自己写了几个带参数的构造方法,因为编译器只有在一个类中没有任何构造方法才会帮我们自动添加一个无参的构造方法,但是我们现在写了带参数的,为了保险起见,我们自己又添加了无参的构造方法,同时也实现了要实例化子类对象,要在子类构造方法中调用父类构造方法,也就是 super(),上面代码都有,可以细细阅读一下。