向上转型和向下转型
向上转型
定义:实际就是创建一个子类对象,将其当作父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
Animal animal = new Cat("元宝", 2);
animal是父类类型,但是可以引用子类对象,因为是从小范围到大范围的转换。
特点:
- 编译时多态性: 父类引用变量可以引用子类对象,编译器会在编译时检查引用变量的类型是否与对象的类型兼容。
- 运行时多态性: 在运行时,根据引用变量所指向的实际对象类型来调用对应的方法,实现方法的多态性。
- 限制方法访问: 向上转型后,只能调用父类中声明的方法,而不能直接调用子类新增的方法。
使用场景:1.直接赋值 2.方法传参 3.方法返回
举例一:直接赋值:
class Animal { void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { void makeSound() { System.out.println("Dog barks"); } } public class Main { public static void main(String[] args) { Animal animal = new Dog(); // 直接赋值 animal.makeSound(); // 调用的是 Dog 类的方法 } }
举例二:方法传参
class Shape { void draw() { System.out.println("draw a shape"); } } class Circle extends Shape { @Override void draw() { System.out.println("draw a circle"); } } class Triangle extends Shape { @Override void draw() { System.out.println("draw a triangle"); } } public class Test { public static void shapeDrawing(Shape s) { s.draw(); } public static void main(String[] args) { Shape s1 = new Circle(); Shape s2 = new Triangle(); shapeDrawing(s1); shapeDrawing(s2);//方法传参,传递不同子类对象 } }
举例三:做返回值
class Vehicle { String getType() { return "Vehicle"; } } class Car extends Vehicle { @Override String getType() { return "Car"; } } class Bike extends Vehicle { @Override String getType() { return "Bike"; } } public class Test { public static Vehicle getType(String type){ if(type.equals("car")){ return new Car(); } else if(type.equals("bike")) { return new Bike(); } else { return new Vehicle(); } } public static void main(String[] args) { Vehicle v1 = getType("car"); Vehicle v2 = getType("bike"); Vehicle v3 = getType("plane"); System.out.println(v1.getType()); System.out.println(v2.getType());//方法返回,返回的实际上可能是子类对象 System.out.println(v3.getType()); } }
向上转型的优点:让代码实现更加简单灵活
向上转型的缺点:不能调用到子类特有的方法
向下转型
将一个子类对象经过向上转型之后当作父类方法使用,在无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用还原为子类对象即可,即向下转换。
class Animal1 { void makeSound() { System.out.println("Animal makes a sound"); } } class Dog1 extends Animal1 { @Override void makeSound() { System.out.println("Dog barks"); } void fetch() { System.out.println("Dog fetch the ball"); } } public class Test1 { public static void main(String[] args) { Animal1 animal1 = new Dog1();//向上转型 //向下转型 if(animal1 instanceof Dog1) { Dog1 dog = (Dog1)animal1;//实例类型检查和向下转型 dog.makeSound();//调用子类的方法 dog.fetch();//调用子类特有的方法 } } }
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛出异常。Java中为了提高向下转型的安全性,引入了instanceof(用于检查一个对象是否是指定类或其子类的实例),如果表达式的结果为true,则可以安全转换。
多态的优缺点
优点
假设有如下代码:
class Shape { //属性。。。 public void draw() { System.out.println("画图形"); } } class Rect extends Shape { @Override public void draw() { System.out.println("⎕"); } } class Circle extends Shape { @Override public void draw() { System.out.println("◉"); } } class Star extends Shape { @Override public void draw() { System.out.println("★"); } }
1.能够降低代码的“圈复杂度”,避免使用大量的if-else
圈复杂度:一种描述一段代码复杂程度的方式。一段代码如果平铺直叙,那么就比较简单容易理解,而如果有很多条件分支或者循环语句,就认为了解起来比较复杂。
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”,如果一个方法的圈复杂度太高,就需要考虑重构
不同公司对于圈复杂度的规范不一样,但一般不会超过10
例如我们现在需要的不只是打印一个形状了,而是多个形状,如果不基于多态,实现代码如下:
public static void drawShapes() { Rect rect = new Rect(); Circle circle = new Circle(); Star star = new Star(); String[] shapes = {"circle", "rect", "circle", "rect", "star"}; for(String shape : shapes) { if(shape.equals("circle")){ circle.draw(); } else if(shape.equals("rect")) { rect.draw(); } else if(shape.equals("star")) { star.draw() } } }
如果使用多态,则不必写这么多的if-else分支语句,代码更简单。
Shape[] shapes = {new Circle(), new Rect(), new Circle(), new Rect(), new Star()}; for(Shape shape : shapes) { shape.draw(); }
2.可扩展能力更强
如果要新增一种形状,使用多态的方式代码改动成本也比较低。
class Triangle extends Shape { @Override public void draw() { System.out.println("▲"); } }
对于类的调用者(主函数),只要创建一个新类的实例就可以了,改动成本很低。
而对于不用多态的情况,就要把drawShapes中的if-else进行一定修改,改动成本更高
缺点
代码运行效率降低
1.属性没有多态性:当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
2.构造方法没有多态性