一、什么是多态
同一件事情作用在不同的对象中,呈现出了不同的效果,这种效果称为多态
例如:
如果我们要打印纸张,同一件事情,打印机通过设置,可以彩色打印 or 黑白打印
二、如何实现多态
2.1 条件
- 必须在继承体系下,向上转型(是is-a的另一种表达,所有的猫、狗都是动物)
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法(动态绑定)
class Father{
public void eat(){
System.out.println("正在吃饭");
}
}
class Son extends Father{
public void eat(){ //方法的重写
System.out.println("正在吃黄焖鸡米饭");
}
}
public class Main {
public static void main(String[] args) {
Father son = new Son();
son.eat();
}
}
2.2 方法的重写
1.方法重写
- 重写(override):也称为覆盖。
- 重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
❤️重写原理理解
-
结合下面方法调用的步骤
-
方法的名字和参数列表称为方法的签名。例如, f(int) 和 f(String)是两个具有相同名字, 不同签名的方法。如果在子类中定义了一个与超类签名相同的方法, 那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。
-
不过,返回类型不是签名的一部分, 因此,在覆盖方法时, 一定要保证返回类型的兼容性。 允许子类将覆盖方法的返回类型定义为原返回类型的子类型或者相同
❤️哪些情况下不能方法重写
- 被private修饰的方法不能重写
- 被static修饰的方法不能重写 -----------> 不依赖对象
- 被final修饰的方法,不能被重写
- 构造方法不能被重写(因为构造方法无法被继承)
- 子类的访问修饰限定符一定要大于等于父类的访问修饰限定符
❤️快捷键
2.方法重写 VS 方法重载
方法重写 | 方法重载 | |
方法名 | 一样 | 一样 |
参数列表(个数、类型、顺序) | 可以不一样,但一定要是父子关系 | 不一样 |
返回类型 | 一样 | 无要求 |
2.3 向上转型
概念:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
是从小范围向大范围的转变
优点:让代码实现更简单灵活。
缺陷:不能调用到子类特有的方法
class Father{
public void eat(){
System.out.println("吃饭");
}
}
class Son extends Father{
public void eat(){
System.out.println("正在吃黄焖鸡米饭");
}
}
public class Main {
public static void main(String[] args) {
Father person = new Son(); //这个调用时合理的,因为子类就是父类,继承关系
person.eat();
}
}
2.4 动态绑定
❤️概念:
让我们来思考一个问题,这里是用Father引用来调用的,但是呈现出来的结果确实调用了子类的 eat ,通过查看字节码文件发现,程序编译的时候确实调用了Father的eat,但是运行的结果是子类的
在编译时,不知道调用哪个方法,等到运行的时候,才能知道,这种过程叫做运行时绑定 / 动态绑定
❤️条件:
- 用父类的引用调用来调用这个重写的方法
- 方法的重写
❤️方式:
- 直接赋值
Father person = new Son();
- 传参
class Father{
public void eat(Father person){
System.out.println("吃饭");
}
}
class Son extends Father{
public void eat(Father person){
System.out.println("正在吃黄焖鸡米饭");
}
}
public class Main {
public static void main(String[] args) {
Son person = new Son();
person.eat(person);
}
}
- 返回值
class Son extends Father{
public Father eat(Son person){
System.out.println("正在吃黄焖鸡米饭");
return person;
}
}
public class Main {
public static void main(String[] args) {
Son person = new Son();
person.eat(person);
}
}
三、扩展内容
3.1 静态绑定
在编译时,通过参数列表的个数、类型、大小等就可以确定是哪个方法,比如方法的重载
3.2 向下转型
概念:创建一个父类对象,用子类的引用来接收,从小范围变为了大范围
语法格式:子类类型 对象名 = new 父类类型()
注意点:
- 除非是调用该子类对象的成员,否则不安全(理解:不是所有的动物都是狗,也可能是猫或其他,万一出现狗引用了猫情况,会报错)
- instanceof,可以规避不安全的情况
- 父类给子类需要强转
class Animal{
String name;
int age;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
public void bark(){
System.out.println(this.name + "在汪汪叫");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
public void mimi(){
System.out.println(this.name + "在喵喵叫");
}
}
public class Main{
public static void main(String[] args) {
Animal animal = new Dog("旺财");
Dog dog = (Dog) animal;
dog.bark();
if (animal instanceof Cat){
Animal animal2 = new Dog("旺财");
Cat cat = (Cat) animal;
cat.mimi();
}
}
}
3.3 代码的优缺点
❤️优点:
- 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
- 可扩展能力更强(如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低)
❤️缺陷:
-
属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 -
不能在构造方法中调用其他方法,可能导致还没有初始化完成,就执行其他方法,即成员变量还是默认值
二、注意点
每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表( method table), 其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候, 虚拟机仅查找这个表就行了