JAVA 七月 09, 2021

9.面向对象之多态

文章字数 8.2k 阅读约需 7 mins. 阅读次数 1000000

一:多态详解

什么是多态

封装,继承与多态是面向对象的三大基本特征,其中多态更是面向对象的灵魂。

那么,什么是多态?

多态首先是建立在继承的基础之上的。简单来说,多态就是:用父类的引用指向子类的对象,其既可以表现出子类独有的状态(通过 override 父类的方法);也可以表现出父类的状态。

来看示例程序:

public class Polymorphic {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Animal cat = new Cat();
        cat.speak(); // 重写父类的speak方法 表现出了子类独有的状态
        cat.sayHello();// 直接调用父类的sayHello方法,表现出了父类的状态
    }
}
class Animal {
    public void speak(){
        System.out.println("我是一个动物");
    }
    public void sayHello(){
        System.out.println("hello");
    }
}
class Cat extends Animal{
    @Override
    public void speak() {
        System.out.println("我是一只猫");
    }
}

该程序输出的结果为:

我是一只猫
hello

静态绑定与动态绑定

我们先来说明下什么是方法的参数,什么是方法的接收者,举个栗子:

obj.f(param);

对于上述的方法调用,obj 是调用方法的对象,也叫做方法的接受者;param 则是方法的参数。

多态遵循一条规则:方法的参数为静态绑定,方法接收者为动态绑定。换句话说,多态只对方法的接收者生效;再换句话说,多态只选择方法接收者的类型,不选择参数的类型。

我们来看示例程序:

ParamBase

public class ParamBase {
}

ParamSub

public class ParamSub extends ParamBase{
}

Base

public class Base {
    public void print(ParamBase param) {
        System.out.println("I am Base,the param is ParamBase.");
    }

    public void print(ParamSub param) {
        System.out.println("I am Base,the param is ParamSub.");
    }
}

Sub

public class Sub extends Base {
    @Override
    public void print(ParamBase param) {
        System.out.println("I am Sub,the param is ParamBase.");
    }

    @Override
    public void print(ParamSub param) {
        System.out.println("I am Sub,the param is ParamSub.");
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Base obj = new Sub();
        ParamBase param = new ParamSub();
        obj.print(param);
    }
}

该程序运行的结果为:

I am Sub,the param is ParamBase.

解释:

obj.print(param); obj 为方法的接收者,也就是动态绑定,它实际上是一个 Sub 类的对象;

param 是方法的参数,也就是静态绑定,传入到方法中的参数类型为 ParamBase。

所以,该程序运行后会输出:I am Sub,the param is ParamBase.

编译时的多态性与运行时的多态性

多态分为编译时的多态性和运行时的多态性,并且静态方法不具有多态性

  • 多态中成员变量的访问特点:编译时看左边,运行时看左边

示例程序:

public class Polymorphic {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Animal cat = new Cat();
        System.out.println(cat.name);
    }
}
class Animal {
    String name = "animal";
}
class Cat extends Animal{
    String name = "cat";
}

程序输出结果为:

animal

解释:

在程序编译时期,首先 JVM 会看向 Animal cat = new Cat(); 这句等号左边的父类引用是否有该变量(name)的定义,如果有则编译成功,如果没有则编译失败;在程序运行时期,对于成员变量,JVM 仍然会看左边的所属类型,获取的是引用父类的变量结果 。

  • 多态中成员方法调用的特点:编译看左边,运行看右边

示例程序:

public class Polymorphic {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Animal cat = new Cat();
        cat.speak(); // 重写父类的speak方法 表现出了子类独有的状态
    }
}
class Animal {
    public void speak(){
        System.out.println("我是一个动物");
    }
}
class Cat extends Animal{
    @Override
    public void speak() {
        System.out.println("我是一只猫");
    }
}

程序输出结果:

我是一只猫

解释:

在程序编译期,首先JVM 会看向 Animal cat = new Cat(); 等号左边所属类型是否有该方法,如果有则编译成功,如果没有则编译失败;在程序运行时,则是要看等号右边的对象是如何实现该方法的,最终输出为右边对象对这个方法重写后的结果。

多态总结

  • 实例方法默认是多态的

  • 在运行时根据 this 来决定调用哪个方法

  • 静态方法没有多态

  • 参数静态绑定,接受者动态绑定

二:设计模式实战:策略模式

策略模式案例:打折策略

我们有一个打折策略