理解多态
有了上面的向上转型,动态绑定,方法重写之后,我们就可以使用多态的形式来设计程序了
我们可以写一些只关注父类的代码,就能够同时兼容各种子类的情况
代码示例:打印多种形状
class Shape { public void draw() { //啥都不用干 } } class Cycle extends Shape { @Override public void draw() { System.out.println("○"); } } class Rect extends Shape { @Override public void draw() { System.out.println("方块"); } } class Flower extends Shape { @Override public void draw() { System.out.println("❀"); } } ********************分割线****************************************** public class Main { public static void drawMap(Shape shape) { shape.draw(); } public static void main(String[] args) { Shape shape1 = new Cycle(); Shape shape2 = new Flower(); Shape shape3 = new Rect(); drawMap(shape1); drawMap(shape2); drawMap(shape3); } }
在这个代码中分割线上方的代码是类的实现者编写的,分割线下方的代码是类的调用者编写的
使用多态的好处是什么?
1)类调用者对类的使用成本进一步降低
- 封装是让类的调用者不需要知道类的实现细节
- 多态能让类的调用者连这个类的类型是什么都不必知道,只需要知道这个对象具有某个方法即可
因此多态可以理解成封装的进一步,让类调用者对类的使用成本进一步降低
2)能够降低代码的“圈复杂度”,避免大量使用if-else
3)可扩展能力强
如果新增一种新的形态,使用多态的方式代码改动成本也比较低
class Triangle extends Shape { @Override public void draw() { System.out.println("△"); } }
向下转型
向上转型是子类对象转成父类对象,向下转型就是父类对象转成子类对象,相比于向上转型来说,向下转型没有那么常见
super关键字
前面的代码由于使用了重写机制,调用到的是子类的方法,如果在子类内部需要调用父类方法怎么办?可以使用super
关键字
super表示获取到父类实例的引用
1)使用super来调用父类的构造方法
2)使用super来调用父类的普通方法
super
和this
的区别
结论用尽量简单的方式使对象进入可工作状态,尽量不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没有构造完成),可能会出现一些隐藏的但又极难发现的问题
无论那种编程语言,多态的核心都是让调用者不必关注对象的具体类型,这是降低用户使用成本的一种方法
抽象类
语法规则
在刚才的打印图形例子中,我们发现父类Shape
中的draw方法好像并没有实际的工作,主要的绘制图形都是由Shape各种子类的draw方法来完成的,像这种没有实际工作的方法,我们可以把他设计成一个抽象方法(abstract method),包含抽象方法的类我们称为抽象类
abstract class Shape { abstract public void draw(); }
- 在draw方法前加上关键字abstract,表示一个抽象方法,同时抽象方法没有方法体(没有{ },不能执行具体代码)
- 对于包含抽象方法的类,必须加上一个abstract关键字表示这是一个抽象类
注意事项
1)抽象类不能直接实例化
2)抽象方法不能是private
3)抽象类中可以包含其他非抽象方法,也可以包含字段,这个非抽象方法和普通方法的规则是一样的,可以被重写,也可以被子类调用
抽象类的作用
抽象类存在的最大意义就是为了被继承
抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法
使用抽象类相当于多了一重编译器的校验
使用抽象类的场景就如上面的代码,实际工作不应该由父类来完成,而应由子类来完成,那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的,但是父类是抽象类在实例化的时候提示错误,让我们今早发现问题
很多语法存在的意义都是为了“预防出错”,例如我们曾经用过的
final
关键字,创建的变量用户不去修改,不就相当于常量吗?但是加上final
,能够在不小心误修改的时候,让编译器提示我们充分利用编译器的校验,在实际开放中非常有意义的