面向对象的三大基本原则之一 ——多态。多态赋予了你的程序无与伦比的强大功能,是面向对象的灵魂!

面向对象的三大特征:

  • 封装
  • 继承
  • 多态

1. 什么是多态

这不是多态:

  1. Person p1 = new Person();

下面是多态!Man 和 Woman 都是 Person 的子类:

  1. Person p2 = new Man();
  2. Person p3 = new Woman();

这就体现了对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
也因为一个子类类型的对象永远也是一个父类类型的对象,因此,当声明一个父类型时,总可以传递一个子类型对象。

多态性可以理解为一个事务的多种形态,= 赋值的右边有多种形态的对象来满足左边的需求。

2. 多态的使用

有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类后的方法。

  • 实例方法默认是多态的,在运行时根据this来决定调用哪个方法,而静态方法没有多态;
  • 参数是静态绑定,接收者是动态绑定;
  • 多态只对方法的接收者生效;
  • 多态只选择接收者的类型, 不选择参数的类型;
  • 例如:HashSet.addAll()
  • 静态方法的调用是通过在编译器静态绑定的,而实例方法的调用是在运行时动态绑定的
  • 多态是运行时行为

假设子类分别覆盖了父类的eat()walk()

  1. Person p2 = new Man();
  2. p2.eat();
  3. p2.walk();

那么上面这样写是ok的,在编译期,这两个方法指的仍是父类Person的实例方法,运行时,子类对象接收到了方法调用的消息(同时可能还有方法的参数),其作为接收者要进行响应,发现子类进行了重写,于是动态调用重写后的方法。

接着这个思路,如果对p2调用只有子类Man中才存在的实例方法,在编译阶段会报错,因为编译时认为p2 属于赋值操作左边的类型,认为是Person类型,而Person并没有定义站着尿尿这个方法。

  1. p2.站着尿尿();

结论

  • 编译时,看左边;运行时,看右边。
  • 多态的使用前提:
    • 类的继承关系
    • 方法的重写

3. 虚拟方法调用(多态的情况下)

子类中重写了父类方法后,在多态情况下,将此时父类的方法称为虚拟方法,在运行时,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的,所以比较虚。

在编译时是父类类型,运行时成了对子类类型上的方法调用,这个过程也称为动态绑定

借用一张尚硅谷免费资源中的PPT:
初级09 -  面向对象:多态 - 图1

4. 设计模式实战:策略模式

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

使用了工厂方法的User(注意这个类不是策略模式必须的,或者说无关):

  1. public class User {
  2. private String name;
  3. private boolean vip;
  4. private User(String name, boolean vip) {
  5. this.name = name;
  6. this.vip = vip;
  7. }
  8. public static User vip(String name) {
  9. return new User(name, true);
  10. }
  11. public static User dios(String name) {
  12. return new User(name, false);
  13. }
  14. public String getName() { return name; }
  15. public boolean isVip() { return vip; }
  16. }

接下来属于策略模式。

一个需要调用这组算法策略的使用者PriceCalculator

  1. public class PriceCalculator {
  2. // 使用策略模式实现三个策略:
  3. // NoDiscountStrategy 不打折
  4. // Discount95Strategy 全场95折
  5. // OnlyVipDiscountStrategy 只有VIP打95折,其他人保持原价
  6. public static int calculatePrice(DiscountStrategy strategy, int price, User user) {
  7. return strategy.discount(price, user); // 实际执行的是子类重写后的方法
  8. }
  9. public static void main(String[] args) {
  10. User user = User.dios("wangpeng");
  11. System.out.println("用户名:" + user.getName());
  12. // 使用什么策略就传入什么策略
  13. int price = calculatePrice(new Discount95Strategy(), 10000, user);
  14. System.out.println("实际价格:" + price);
  15. }
  16. }

策略的父类DiscountStrategy

  1. public class DiscountStrategy {
  2. // 虽然正常情况下这个基类中的策略都会被某个具体策略给Override,但我认为目的有二:
  3. // 1. 被子类覆盖从而实现多态调用
  4. // 2. 用来兜底,抛出个异常
  5. public int discount(int price, User user) {
  6. throw new UnsupportedOperationException();
  7. }
  8. }

没有折扣的NoDiscountStrategy

  1. public class NoDiscountStrategy extends DiscountStrategy {
  2. @Override
  3. public int discount(int price, User user) {
  4. return price;
  5. }
  6. }

打95折的Discount95Strategy

  1. public class Discount95Strategy extends DiscountStrategy {
  2. @Override
  3. public int discount(int price, User user) {
  4. return (int)(price * 0.95);
  5. }
  6. }

Vip独享的打折策略OnlyVipDiscountStrategy

  1. public class OnlyVipDiscountStrategy extends DiscountStrategy {
  2. @Override
  3. public int discount(int price, User user) {
  4. if (user.isVip()) {
  5. return (int) (price * 0.95);
  6. } else {
  7. return price;
  8. }
  9. }
  10. }

策略模式和模板方法模式有些类似,二者的界限在于:父类中是否包含“骨架”逻辑

策略模式的父类中几乎没有代码(大多数情况是个抽象方法),但是模版方法模式的父类中有一个“模板方法”,包含很多“骨架”逻辑代码。


参考文章:
Java中为什么静态方法不能被重写?为什么静态方法不能隐藏实例方法?