在之前的文章,我们分别介绍了类与对象、面向对象三大特征的封装、以及继承(一)、继承(二)。这一篇文章,我们介绍Java面向对象三大特征的最后一个——多态。
多态
多态的概述
- 概念:完成某个行为,当不同的对象去完成时会产生出不同的状态;
例如猫和狗一样喊叫,虽然行为是相同的,但是状态(行为表现)不同。
- 体现:在代码运行时,当传递不同类对象时,会调用对应各自类中的方法。
例如调用Cat.shout() 和 Dog.shout()。
多态的缺陷
-
代码的运行效率降低。
-
属性没有多态性,当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 。
-
构造方法没有多态性 。
多态实现的三个条件
-
要有继承(包括接口的实现)(前提条件)
(接口会在下一文介绍) -
子类必须对父类方法进行重写(前提条件)
-
有向上转型,并通过父类的引用调用重写的方法。
当编译期类型是父类,运行期类型是子类时,被称为父类引用指向子类对象。
下面我们先来了解重写,再来了解向上转型。
重写和重载
概念
-
方法的重写是指子类重新定义父类中已经存在的方法,而方法的重载是指在同一个类中定义多个方法,具有相同的名称但是不同的参数。
-
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
(注意:重写也称覆盖Overrride,重载Overload)
重写
使用情况
父类中的方法,无法满足子类的业务需求,子类需要对方法进行重写(覆盖)。
重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验.例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。
使用@Override便于检查。
构成重写所需的满足条件
第一:有继承关系的两个类
第二:具有相同方法名、返回值类型(或具有父子关系)、形式参数列表。
比如父类方法中返回值是Animal,子类方法中返回值是Animal的子类Dog
第三:访问权限不能小于父类权限。
第四:抛出异常不能更多。
注意:父类的构造方法,static和final方法不能重写,成员变量不存在重写。
这是访问修饰符权限表。
重写的设计原则
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
重载
- 概念:一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同。
- 条件:被重载的方法必须改变参数列表(参数个数或类型不一样)。
- 体现:同样的一个方法能够根据输入数据的不同,做出不同的处理。
- 重载函数的返回值可变可不变。
- 可以改变访问修饰符。
重写与重载的区别
动态绑定
下面我们来介绍一下动态绑定。
当调用一个方法名时,究竟应该调用哪个方法,这件事情叫做绑定。绑定表明了调用一个方法的时候,我们使用的是哪个方法。
绑定有两种:
一种是早绑定,又称静态绑定,这种绑定在编译的时候就确定了;另一种是晚绑定,即动态绑定。
动态绑定在运行的时候根据变量当时实际所指的对象的类型动态决定调用的方法。Java默认使用动态绑定。
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表重载。
动态绑定:称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。 这也是多态的特征。
向上转型和向下转型
向上转型
指父类引用类型变量 指向 子类对象,
如父类Animal类型变量指向子类Dog对象。
Animal animal = new Dog();
如果在这里不清晰创建对象过程,可到Java——从建立一个类开始(类与对象总结)里面了解了解。
-
实际就是创建一个子类对象,将其当成父类对象来使用。
-
实现对不同子类对象的统一处理。
-
只用向上转型可以实现多态性。
-
使用场景:
-
直接赋值:父类类型 对象名 = new 子类类型( ),子类对象赋值给父类对象。
-
方法传参:若一个方法的形参为父类型引用,则可以接收任意子类对象。
-
方法返回:若一个方法的返回值是父类型,则可以返回任意子类对象。
-
-
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
向下转型
指将父类的引用变量转化为具体的子类对象。
如上面的父类变量animal转化为子类对象dog。
Animal animal = new Dog();
Dog dog = (Dog) animal;
- 格式: 子类 对象名 = (子类)父类引用变量。
- 应用情况:当进行向上转型后,若还需要调用子类特有的方法,可以将父类引用再还原为子类对象,即向下转型。
- 无法根据此格式将子类对象还原为父类对象。
- Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。用来判断左边的对象是否为右边类的实例,是返回true,不是返回false。
示例代码:
class Animal {String name;int age;public Animal(String name, int age){this.name = name;this.age = age;}public void eat(){System.out.println(name + "吃饭");}
}
class Cat extends Animal{public Cat(String name, int age){super(name, age);}@Overridepublic void eat(){System.out.println(name+"吃鱼~~~");}void mew(){System.out.println(name + "喵喵喵~~~");}
}
class Dog extends Animal {public Dog(String name, int age){super(name, age);}@Overridepublic void eat(){System.out.println(name+"吃骨头~~~");}void bark(){System.out.println(name + "汪汪汪~~~");}
}///分割线//public class Main {// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
// 注意:此处的形参类型必须时父类类型才可以public static void main(String[] args) {Cat cat = new Cat("元宝",2);Dog dog = new Dog("小七", 1);// 向上转型Animal animal = cat;animal.eat();animal = dog;animal.eat();if(animal instanceof Cat){cat = (Cat)animal;cat.mew();}if(animal instanceof Dog){dog = (Dog)animal;dog.bark();}}}
输出:
下面是《Java面向对象三大特征》这几篇文章的参考链接:
(1) (2) (3) (4) (5)
(6) (7) (8) (9) (10)
(11) (12) (13) (14)