首页 > 编程笔记

什么是多态,Java多态详解

面向对象有三大特征:封装、继承和多态,在前面的章节中我们已经学习了封装和继承,本节我们来学习多态。

多态的概念本身是比较抽象的,简单解释就是一个事物有多种表现形态,具体到 Java 程序中,就是定义一个方法,在具体的生产环境中根据不同的需求呈现出不同的业务逻辑,我们通过下面这个例子来理解什么是多态。

假设一个业务场景,书店的普通会员买书,收银员为该会员计算优惠折扣,思路是:先创建普通会员类,定义买书方法描述该会员买书的优惠折扣,再创建收银员类,将普通会员对象作为成员变量,在结算方法中调用普通会员的买书方法,代码所下:
public class OrdinaryMember {
    public void buyBook() {
        System.out.println("普通会员买书打9折");
    }
}

public class Cashier {
    private OrdinaryMember ordinaryMember;
    //getter、setter方法

    public void settlement() {
        this.ordinaryMember.buyBook();
    }
}

public class Test {
    public static void main(String[] args) {
        OrdinaryMember ordinaryMember = new OrdinaryMember();
        Cashier cashier = new Cashier();
        cashier.setOrdinaryMember(ordinaryMember);
        cashier.settlement();
    }
}
程序的运行结果是:

普通会员买书打9折

用户希望买书能享受更大优惠,就把普通会员升级为超级会员,买书可以享受 6 折优惠。现在用超级会员再次购书,这时候就需要创建超级会员类,并且 Cashier 类和 Test 类也需要做相应的修改,代码如下:
public class SuperMember {
    public void buyBook() {
        System.out.println("超级会员买书打6折");
    }
}

public class Cashier {
    private SuperMember superMember;
    //getter、setter方法

    public void settlement() {
        this.superMember.buyBook();
    }
}

public class Test {
    public static void main(String[] args) {
        SuperMember superMember = new SuperMember();
        Cashier cashier = new Cashier();
        cashier.setSuperMember(superMember);
        cashier.settlement();
    }
}
程序运行结果是:

超级会员买书打6折

这种方式存在明显不足,当需求发生改变时需要频繁地修改代码,代码的扩展性、维护性较差。

使用多态可以进行优化,多态的思路是:创建 Member 类,作为 OrdinaryMember 和 SuperMember 的父类,在 OrdinaryMember 和 SuperMember 中分别对父类方法进行重写,在 Cashier 类中定义 Member 类型的成员变量,代码如下所示:
public class Member {
    public void buyBook() {
    }
}

public class OrdinaryMember extends Member {
    //重写父类方法
    @Override
    public void buyBook() {
        // TODO Auto-generated method stub
        System.out.println("普通会员买书打9折");
    }
}

public class SuperMember extends Member{
    //重写父类方法
    @Override
    public void buyBook() {
        // TODO Auto-generated method stub
        System.out.println("超级会员买书打6折");
    }
}

public class Cashier {
    private Member member;
    //getter、setter方法

    public void settlement() {
        this.member.buyBook();
    }
}

public class Test {
    public static void main(String[] args) {
        Member member = new OrdinaryMember();
        Cashier cashier = new Cashier();
        cashier.setMember(member);
        cashier.settlement();
    }
}
同样的会员升级,从普通会员升级为超级会员,此时就不需要修改 Cashier 类,只需要在 Test 类的 main 方法中作出修改:“Member member = new SuperMember();”,代码如下:
public class Test {
    public static void main(String[] args) {
        Member member = new SuperMember();
        Cashier cashier = new Cashier();
        cashier.setMember(member);
        cashier.settlement();
    }
}
如果业务需要扩展,想升级为其他类型的会员,只需要创建对应的会员类并继承 Member 类,然后在 main 方法中作出修改,将该会员的实例化对象赋给 member 即可。

这就是多态,从 main 方法的角度来看这段代码,我们不去定义具体的 OrdinaryMember 或者 SuperMember,而是定义 Member,然后将具体的实例化对象赋给 Member,即同一个 Member 有多种表现形式。

多态的使用

上述代码中有一行:
Member member = new SuperMember();
它用于定义父类变量 member,然后将子类 SuperMember 的实例化对象赋值给 member,即父类引用指向子类对象,是多态的具体表现形式。

在实际开发中,多态主要有两种表现形式:一种是定义方法时形参为父类,调用方法时传入的参数为子类对象;另一种是定义方法时返回值的数据类型为父类,调用方法时返回子类对象。

定义方法时形参为父类,调用方法时传入的参数为子类对象,例如:
public class Cashier {
    public void settlement(Member member) {
        member.buyBook();
    }
}

public class Test {
    public static void main(String[] args) {
        OrdinaryMember ordinaryMember = new OrdinaryMember();
        SuperMember superMember = new SuperMember();
        Cashier cashier = new Cashier();
        cashier.settlement(ordinaryMember);
        cashier.settlement(superMember);
    }
}
运行结果为:

普通会员买书打9折
超级会员买书打6折


定义方法时返回值的数据类型为父类,调用方法时返回子类对象,例如:
public class Cashier {
    public Member getMember(String name) {
        if(name.equals("ordinaryMember")) {
            return new OrdinaryMember();
        }else {
            return new SuperMember();
        }
    }
}
无论是上述的哪种方式,成立的前提都是因为父类引用可以指向子类对象,即“Member member = new OrdinaryMember ();”或者“Member member = new SuperMember();”。也就是说我们可以把一个  OrdinaryMember 对象或者一个 SuperMember 对象当作 Member 对象来使用。

在现实世界中也是合理的,比如张三是普通会员,我们说张三是会员也是没有错的。但是反过来就行不通了,如果我们说会员是张三,这句话从逻辑上来讲是行不通的。因为如果李四办理了超级会员,那么李四也是会员,同理王五也可以是会员,所以直接说会员是张三就以偏概全了。既然在现实世界中行不通,那么在程序的世界中也是行不通的,即我们不能把一个子类引用指向父类对象,如图 1 所示。


图 1 不能把一个子类引用指向父类对象

这是一种数据类型错误,例如不能把 Member 转为 OrdinaryMember。

但是如果我们一定要将 Member 转为 OrdinaryMember 呢?我一定要说会员就是张三怎么办?好吧,既然你这么强势,那就随你吧!强制性地把 Member 转为 OrdinaryMember,这种方式叫作强制类型转换,需要在目标对象前加括号,括号内注明强制转换之后的数据类型,代码如下:
public class Test {
    public static void main(String[] args) {
        OrdinaryMember ordinaryMember = (OrdinaryMember) new Member();
    }
}
综上所述,具有父子级关系的两个对象可以相互转换,子类转父类即父类引用指向子类对象,可以自动完成,无需强制转换。例如我们说张三是会员,这句话逻辑上是行得通的,同时也叫向上转型。父类转子类即子类引用指向父类对象,不能自动完成转换,需要强制转换。例如我们说会员是张三,以偏概全了,需要强制干预,这种方式也叫向下转型。

抽象方法和抽象类

回到前面我们举过的例子:通过子类重写父类方法的形式实现多态,从而提高程序的扩展性,父类 Member 以及两个子类 OrdinaryMember 和 SuperMember 的定义为:
public class Member {
    public void buyBook(){
    }
}

public class OrdinaryMember extends Member {
    //重写父类方法
    @Override
    public void buyBook() {
        // TODO Auto-generated method stub
        System.out.println("普通会员买书打9折");
    }
}

public class SuperMember extends Member{
    //重写父类方法
    @Override
    public void buyBook() {
        // TODO Auto-generated method stub
        System.out.println("超级会员买书打6折");
    }
}
观察上述 3 个类,OrdinaryMember 和 SuperMember 会分别对 Member 的 buyBook() 方法进行重写,也就是说无论 Member 的 buyBook() 方法里写了什么,最后都会被子类所覆盖,所以 Member 的 buyBook() 方法体就是无意义的。那么我们就可以只声明 buyBook() 方法,而不需要定义 buyBook() 的方法体,这种没有方法体的方法叫作抽象方法。

声明抽象方法时需要添加 abstract 关键字,例如:
public abstract void buyBook ();
一旦类中定义了抽象方法,则该类也必须声明为抽象类,需要在类定义处添加 abstract 关键字,例如:
public abstract class Member {
    public abstract void buyBook();
}
抽象类与普通类的区别是抽象类不能被实例化,抽象方法与普通方法的区别是抽象方法没有方法体。抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类。即我们可以在抽象类中定义普通方法,但是在普通类中不能定义抽象方法。

既然抽象类不能被实例化,抽象方法也没有方法体,那么为什么要创建抽象类和抽象方法呢?到底有什么用呢?

抽象类和抽象方法需要结合多态来使用,我们知道构建多态的基础是类的继承和方法重写。既然有重写就意味着父类方法只需要声明,不需要具体的实现,具体实现由子类来完成,父类只是一个抽象的概念或者模板。那么我们就可以把父类定义为抽象类,需要被子类重写的方法定义为抽象方法,并针对这个抽象的概念进行编程。

在具体执行时,给父类赋予不同的子类就会实现不同的功能,这就是多态的意义,即抽象类和抽象方法的作用。在实际开发中我们需要创建抽象类的子类来完成开发,例如:
public abstract class Member {
    public abstract void buyBook();
}

public class OrdinaryMember extends Member {
    //实现父类的抽象方法
    @Override
    public void buyBook() {
        // TODO Auto-generated method stub
        System.out.println("普通会员买书打9折");
    }
}

public class SuperMember extends Member{
    //实现父类的抽象方法
    @Override
    public void buyBook() {
        // TODO Auto-generated method stub
        System.out.println("超级会员买书打6折");
    }
}

public class Test {
    public static void main(String[] args) {
        Member member = new OrdinaryMember();
        member.buyBook();
        member = new SuperMember();
        member.buyBook();
    }
}
运行结果为:

普通会员买书打9折
超级会员买书打6折

继承了抽象类的子类必须重写父类的抽象方法,以完成具体的方法实现,如下图所示:


如果子类也是抽象类,可以不用重写父类的抽象方法,如下图所示:

推荐阅读