我将把多态分为以下几点进行解剖和理解:
目录
一、多态
1、多态的概念
说到多态,按照文字的表面理解,我们通常会认为是”多种不同形态“。
具体点多态是当不同的对象去执行完成同一件事情时,会产生不同的状态。
比如现实生活中,猫和狗都是动物,它们会有一些共有的特性(饮食习惯,身体构造之类),虽说猫和狗一定会进食,但作为物种的不同,狗会吃狗粮,猫会吃猫粮;狗和猫都会叫,但是狗是”汪汪汪“地叫,而猫是”喵喵喵“地叫。
比如我们平时使用的打印机,打印机其中会有黑白打印机和彩色打印机,同为打印机的它们都能打印东西,但是同样的打印行为,打印出来的纸张效果是不一样。
总结以上的特点,同一件事情发生在不同的对象身上,会产生不同的行为结果,这就是多态。
要具体使用多态的这种特性,需要在特定条件下才能使用,接下来我将逐一介绍实现的条件和过程。
2、多态的前提条件
在java中要实现多态,必须要满足如下几个条件,缺一不可:
- 必须在继承体系下
- 子类必须要对父类中方法进行重写(父类得引用对应的子类对象)
- 通过父类的引用调用重写的方法
多态的实现:
运行代码时,传递不同的子类对象给父类对象,之后通过调用父类和子类中构成重写的方法,就会执行对应类的方法。期间父类引用子类对象会发生向上转型。
注意:
编译器在编译代码的时候,并不知道父类调用重写方法时,具体调用的是哪个子类对象,在程序运行起来的时候,会先判断其父类的引用对象类型,再根据实际的类型调用其相应的方法。运行过程中即会发生“动态绑定”。(下面将会介绍)
此处你们可能会发现代码中的父类Animal和子类Dog和Cat内,都存在eat()方法,而且除了内部语法不一样之外,方法名,返回类型,甚至连参数列表都一模一样。这些方法之间构成了方法的重写。
之前的方法重载是参数列表不一样,返回类型和变量名一样。
二、方法的重写
1、什么是重写?
重写(override):
重写也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
2、重写的规则
1、子类在重写父类的方法时,一般必须与父类方法原型一致:修饰符 返回值类型 方法名(参数列表) 要完全一致。
2、其实被重写的方法返回值类型可以不同,但是必须是具有父子关系的。这种类型关系称为:方法重写的协变。(协变类型,如果重写的时候,返回值之间构成继承关系,则称为协变)
3、子类的访问权限等级不能比父类的访问权限更低,例如:如果父类的一个方法被声明public,那么在子类中重写该方法就不能声明为 protected,只能为public了。或者父类一个方法被声明为protected,那么子类的重写方法可以声明为pubilc或protected。(最起码与父类的访问权限等级相等或更高)。
4、父类的成员方法只能被它的子类重写。
5、子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
6、子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
7、父类被static、private修饰的方法和构造方法都不能被重写。
8、声明为 final 的方法不能被重写。
9、重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.
3、重写和重载的区别
区别点 | 重载(override) | 重写(override) |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
访问限定符 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
方法的重载:一个类的多态性表现
方法之间构成的重载,其方法的返回类型不作要求,但变量名需一样,而参数列表一定要有所不同(参数数量、参数类型、参数顺序上的不同)。
方法的重写:子类与父类的一种多态性表现。
=========================================================================
我们在编译时调用父类的重写方法,编译器并不会知道父类对象会具体调用引用的哪个子类重写方法,但是在运行后,却能准确地调用相应的子类重写方法。这是为什么呢?
这时就引出了一个名词---“动态绑定”
动态绑定 :
静态绑定:
4、避免在构造方法中调用重写的方法
下面以一个代码为例:
此代码中:
- 构造Cat的对象时,会调用Animal的构造方法
- Animal的构造方法中调用了eat方法, 此时会触发动态绑定, 会调用到 Cat中的eat
以后在我们写代码的时候,尽量不要出现类似的代码,可能会出现一些隐藏的但是又极难发现的问题。我们要避免在构造方法中调用重写的方法了!
三、向上转型和向下转型
1、向上转型
以animal为例,animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。 (注意:继承关系,一次只能继承一个类)
我们知道平时new一个对象后,可能调用对应类里的成员方法。
还需注意的是不能调用其他类中的方法,即使两个类之间构成了继承关系!
2、向上转型的三种方式
2.1、直接赋值
public class TestDemo6 {
public static void main(String[] args) {
Animal animal= new Cat();
animal.eat();
}
}
2.2、通过方法的传参
public static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
eat(new Cat());
}
2.3、方法返回值
public static Animal eat(){
return new Cat("小黄");
}
public static void main(String[] args) {
eat();
}
public class TestAnimal {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eatFood(Animal a){
a.eat();
}
// 3. 作返回值:返回任意子类对象
public static Animal buyAnimal(String var){
if("狗" == var){
return new Dog("狗狗",1);
}else if("猫" == var){
return new Cat("猫猫", 1);
}else{
return null;
}
}
public static void main(String[] args) {
// 1. 直接赋值:子类对象赋值给父类对象
Animal cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal.eat();
animal = buyAnimal("猫");
animal.eat();
}
3、向下转型
向下转型和向上转型的转向:
向下转型其实是很不安全的。万一在使用的时候转换失败,运行时就会抛异常。
四、多态的优缺点
1、多态的优点
1.1、降低代码圈复杂度
例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:
public static void drawShapes() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
如果使用使用多态, 再通过一个for-each循环则不必写这么多的 if - else 分支语句, 代码更简单.
public static void drawShapes() {
// 创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
//Shape为自定义类类型,定义一个自定义类数组,每一个元素都是一个实例化的对象,
//发生了向上转型
1.2、可扩展能力更强
通过多态,我们可以很方便地对其功能的扩展,不用过于麻烦地改动。
以上面打印图形形状为例,如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
新增打印三角形
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
2、 多态的缺点
五、总结(多态的注意事项)
有关Java中的三大特性介绍,到这里就已经完结了。
如果文章对各位看官有所帮助,希望能点一个免费赞支持支持!
要是有哪个地方写的不好,或哪个地方出现错误,也希望各位大佬能帮忙纠正错误,我会多加改进
让我们一起努力,一起加油吧