1、多态的概念:
当不同的对象去完成某个相同的行为时,会产生不同的状态,如:打印机分为黑白打印机和彩色打印机,在黑白打印机情况下打出来为黑白,在彩色打印机情况下打印出来为彩色
2、多态实现条件:
1、必须在继承体系下
2、子类必须要对父类中方法进行重写(重写详解)
3、通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法,体现如下:
结果:
3、多态的优缺点:
现有如下代码:
class Shape{
//属性...
public void draw(){
System.out.println("画图形!");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("画一个♦");
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("画一个●");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("画一个❀");
}
}
多态优点:
1、能够降低代码的“圈复杂度”,避免大量使用 if - else
圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,那么就比较简单容易理解,而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为 "圈复杂度", 如果一个方法的圈复杂度太高,就需要考虑重构。
不同公司对于代码的圈复杂度的规范不一样, 一般不会超过 10
如果我们现在要重复打印多个形状,如果不基于多态,实现代码如下:
public class drawShapes {
public static void main(String[] args) {
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();
}
}
}
}
如果使用多态,则不必写很多 if - else 分支语句,如下:
public class drawShapes {
public static void main(String[] args) {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
Shape[] shapes = {cycle, rect, cycle, rect,flower};
for (Shape shape : shapes) {
shape.draw();
}
}
}
2、可扩展能力更强
如果要新增一种形状,使用多态的方式代码改动成本也比较低,如下:
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
对于类的调用者来说(drawShapes方法),只要创建一个新类的实例就可以了,改动成本很低
而对于不用多态的情况,就要把 drawShapes 中的 if - else 进行修改,改动成本更高
多态缺点:
1、属性没有多态型
当父类和子类都有同名的属性时,通过父类引用,只能引用父类自己的成员属性
2、构造方法没有多态性,具体如下:
避免在构造方法中调用重写的方法
我们创建两个类,B是父类,D是子类,D中重写func方法,并且在B的构造方法中调用func
class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
结果:
分析:
构造D对象的同时,会调用B的构造方法
B的构造方法中调用了func方法,此时会触发动态绑定,会调用到D中的func
此时D对象自身还没有构造,此时num处在未初始化的状态,值为0,如果具备多态性,num的值应为1
所以在构造函数内,尽量避免使用实例方法,除了final和private方法
结论:
“用尽量简单的方式使对象进入可工作状态”,尽量不要在构造器中调用方法,如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成,可能会出现一些隐藏的但又极难发现的问题