3.向上转型和向下转型
向上转型和向下转型是我们学习多态的一个重要部分,在多态实现条件中需要满足通过父类的引用调用子类重写的方法,这时我们就需要用到向上转型。
3.1 向上转型
在上述代码中有一个打印机的多态代码。在测试类的主函数中这样的代码:
ColorPrinter colorPrinter = new ColorPrinter("彩色"); Printer printer = colorPrinter;
我们知道 ColorPrinter 是子类,Printer 是父类,实例化子类,把子类实例化的对象赋值给了父类类型对象,这就是向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
Printer printer = new ColorPrinter("彩色");
还可以将其语法格式分开写,就是先实例化一个子类,然后将子类对象赋值给父类类型的变量
ColorPrinter colorPrinter = new ColorPrinter("彩色"); Printer printer = colorPrinter;
Printer 是父类类型,但是可以引用子类对象,因为是小范围向大范围的转换
向上转型:
只能访问父类中的成员属性
访问方法时如果子类中没有,则访问父类的,如果父类中也没有,则直接报错
访问方法时如果父类中没有 子类中有,则直接报错
访问方法时如果父类与子类中的方法重写,则访问子类的
访问方法时如果父类与子类中的方法重载,访问子类的重载方法,则直接报错
使用场景:
直接赋值
方法传参
方法返回
class Printer { public String name; Printer(String name) { this.name = name; } public void print() { System.out.println(name+"打印"); } } class ColorPrinter extends Printer { ColorPrinter(String name) { super(name); } @Override public void print() { System.out.println(name+"打印彩色的"); } } class BWPrinter extends Printer { BWPrinter(String name) { super(name); } @Override public void print() { System.out.println(name+"打印黑白的"); } }
还是拿这些继承体系下的类来举例
①直接赋值
public class Test { public static void main(String[] args) { Printer printer = new ColorPrinter("彩色打印机"); printer.print(); } }
通过 Test类 的主方法中 Printer printer = new ColorPrinter("彩色打印机") 这句代码直接赋值,来完成了向上转型。
②方法传参
public class Test { public static void fun(Printer printer) { printer.print(); } public static void main(String[] args) { ColorPrinter colorPrinter = new ColorPrinter("彩色打印机"); fun(colorPrinter); } }
将实例化的子类对象通过方法传参的形式,传给父类对象,来完成向上转型。
③方法返回
public class Test { public static Printer buyPrinter(String name) { if ("彩色打印机".equals(name)) { return new ColorPrinter("彩色打印机"); } else if ("黑白打印机".equals(name)) { return new BWPrinter("黑白打印机"); } else { return null; } } public static void main(String[] args) { Printer printer = buyPrinter("彩色打印机"); printer.print(); } }
通过方法返回实例化对象的方式,进行向上转型。根据方法的形参来判断需要实例化那个子类对象
注:父类引用哪个子类,当通过父类引用去访问重写的方法时,则会访问父类引用的子类中的重写方法
3.2 向下转型
向下转型:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换
class Printer { public String name; Printer(String name) { this.name = name; } public void print() { System.out.println(name+"打印"); } } class ColorPrinter extends Printer { ColorPrinter(String name) { super(name); } @Override public void print() { System.out.println(name+"打印彩色的"); } public void print2() { System.out.println(name+"激光打印"); } } public class Test { public static void main(String[] args) { Printer printer = new ColorPrinter("彩色打印机"); printer.print(); //printer.print2();直接报错 //此时可以采用向下转型 //如果直接向下转型就很不安全,我们就需要instanceof来判断 if (printer instanceof ColorPrinter) { ColorPrinter colorPrinter = (ColorPrinter) printer; colorPrinter.print2(); } } }
4.多态的优缺点
4.1 多态的优点
假设有如下代码:
class Draw { public void fun() { System.out.println("画画"); } } class Square extends Draw { public void fun() { System.out.println("正方形"); } } class Circular extends Draw { public void fun() { System.out.println("圆"); } }
多态的好处:
①能够降低代码的 "圈复杂度", 避免使用大量的 if - else
那么什么是圈复杂度呢?
圈复杂度是一种描述一段代码复杂程度的方式。一段代码如果平铺直叙,那么就比较简单容易理解。而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂。因此我们可以计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度"。如果一个方法的圈复杂度太高, 就需要考虑重构。
如果我们现在需要打印多个图形,如果不用多态,就需要用到多个 if - else :
public class Test { public static void main(String[] args) { Square square = new Square(); Circular circular = new Circular(); String[] arr = {"square","circular"}; for (String tmp : arr){ if (tmp.equals("square")) { square.fun(); } else if(tmp.equals("circular")) { circular.fun(); } } } }
如果使用使用多态, 则不必写这么多的 if - else 分支语句:
public class Test { public static void func(Draw draw) { draw.fun(); } public static void main(String[] args) { func(new Square()); func(new Circular()); } }
②可扩展能力更强
如果要新增一种新的形状,使用多态的方式代码改动成本也比较低
对于类的调用者来说,只要创建一个新类的实例就可以了,改动成本很低
4.2 多态的缺点
多态缺陷:代码的运行效率降低。 1. 属性没有多态性 当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 2. 构造方法没有多态性
5.避免在构造方法中调用重写的方法
class Draw { public Draw() { fun(); } public void fun() { System.out.println("画画"); } } class Square extends Draw { public Square() { super(); } int num = 1; public void fun() { System.out.println("正方形"+num); } } public class Test { public static void main(String[] args) { Draw draw = new Square(); } }
运行结果:
构造 Square 对象的同时, 会调用 Draw 的构造方法.
Square 的构造方法中调用了 fun 方法, 此时会触发动态绑定, 会调用到 Draw 中的 fun
此时 Square 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0。如果具备多态性,num的值应该是1.
所以在构造函数内,尽量避免使用实例方法,除了final和private方法。