首页 > 编程笔记 > Java笔记 阅读:83

什么是多态,Java多态详解

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

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

假设一个业务场景,书店的普通会员买书,收银员为该会员计算优惠折扣,思路是:先创建普通会员类,定义买书方法描述该会员买书的优惠折扣,再创建收银员类,将普通会员对象作为成员变量,在结算方法中调用普通会员的买书方法,代码所下:
  1. public class OrdinaryMember {
  2. public void buyBook() {
  3. System.out.println("普通会员买书打9折");
  4. }
  5. }
  6.  
  7. public class Cashier {
  8. private OrdinaryMember ordinaryMember;
  9. //getter、setter方法
  10.  
  11. public void settlement() {
  12. this.ordinaryMember.buyBook();
  13. }
  14. }
  15.  
  16. public class Test {
  17. public static void main(String[] args) {
  18. OrdinaryMember ordinaryMember = new OrdinaryMember();
  19. Cashier cashier = new Cashier();
  20. cashier.setOrdinaryMember(ordinaryMember);
  21. cashier.settlement();
  22. }
  23. }
程序的运行结果是:

普通会员买书打9折

用户希望买书能享受更大优惠,就把普通会员升级为超级会员,买书可以享受 6 折优惠。现在用超级会员再次购书,这时候就需要创建超级会员类,并且 Cashier 类和 Test 类也需要做相应的修改,代码如下:
  1. public class SuperMember {
  2. public void buyBook() {
  3. System.out.println("超级会员买书打6折");
  4. }
  5. }
  6.  
  7. public class Cashier {
  8. private SuperMember superMember;
  9. //getter、setter方法
  10.  
  11. public void settlement() {
  12. this.superMember.buyBook();
  13. }
  14. }
  15.  
  16. public class Test {
  17. public static void main(String[] args) {
  18. SuperMember superMember = new SuperMember();
  19. Cashier cashier = new Cashier();
  20. cashier.setSuperMember(superMember);
  21. cashier.settlement();
  22. }
  23. }
程序运行结果是:

超级会员买书打6折

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

使用多态可以进行优化,多态的思路是:创建 Member 类,作为 OrdinaryMember 和 SuperMember 的父类,在 OrdinaryMember 和 SuperMember 中分别对父类方法进行重写,在 Cashier 类中定义 Member 类型的成员变量,代码如下所示:
  1. public class Member {
  2. public void buyBook() {
  3. }
  4. }
  5.  
  6. public class OrdinaryMember extends Member {
  7. //重写父类方法
  8. @Override
  9. public void buyBook() {
  10. // TODO Auto-generated method stub
  11. System.out.println("普通会员买书打9折");
  12. }
  13. }
  14.  
  15. public class SuperMember extends Member{
  16. //重写父类方法
  17. @Override
  18. public void buyBook() {
  19. // TODO Auto-generated method stub
  20. System.out.println("超级会员买书打6折");
  21. }
  22. }
  23.  
  24. public class Cashier {
  25. private Member member;
  26. //getter、setter方法
  27.  
  28. public void settlement() {
  29. this.member.buyBook();
  30. }
  31. }
  32.  
  33. public class Test {
  34. public static void main(String[] args) {
  35. Member member = new OrdinaryMember();
  36. Cashier cashier = new Cashier();
  37. cashier.setMember(member);
  38. cashier.settlement();
  39. }
  40. }
同样的会员升级,从普通会员升级为超级会员,此时就不需要修改 Cashier 类,只需要在 Test 类的 main 方法中作出修改:“Member member = new SuperMember();”,代码如下:
  1. public class Test {
  2. public static void main(String[] args) {
  3. Member member = new SuperMember();
  4. Cashier cashier = new Cashier();
  5. cashier.setMember(member);
  6. cashier.settlement();
  7. }
  8. }
如果业务需要扩展,想升级为其他类型的会员,只需要创建对应的会员类并继承 Member 类,然后在 main 方法中作出修改,将该会员的实例化对象赋给 member 即可。

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

多态的使用

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

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

定义方法时形参为父类,调用方法时传入的参数为子类对象,例如:
  1. public class Cashier {
  2. public void settlement(Member member) {
  3. member.buyBook();
  4. }
  5. }
  6.  
  7. public class Test {
  8. public static void main(String[] args) {
  9. OrdinaryMember ordinaryMember = new OrdinaryMember();
  10. SuperMember superMember = new SuperMember();
  11. Cashier cashier = new Cashier();
  12. cashier.settlement(ordinaryMember);
  13. cashier.settlement(superMember);
  14. }
  15. }
运行结果为:

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


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

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


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

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

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

抽象方法和抽象类

回到前面我们举过的例子:通过子类重写父类方法的形式实现多态,从而提高程序的扩展性,父类 Member 以及两个子类 OrdinaryMember 和 SuperMember 的定义为:
  1. public class Member {
  2. public void buyBook(){
  3. }
  4. }
  5.  
  6. public class OrdinaryMember extends Member {
  7. //重写父类方法
  8. @Override
  9. public void buyBook() {
  10. // TODO Auto-generated method stub
  11. System.out.println("普通会员买书打9折");
  12. }
  13. }
  14.  
  15. public class SuperMember extends Member{
  16. //重写父类方法
  17. @Override
  18. public void buyBook() {
  19. // TODO Auto-generated method stub
  20. System.out.println("超级会员买书打6折");
  21. }
  22. }
观察上述 3 个类,OrdinaryMember 和 SuperMember 会分别对 Member 的 buyBook() 方法进行重写,也就是说无论 Member 的 buyBook() 方法里写了什么,最后都会被子类所覆盖,所以 Member 的 buyBook() 方法体就是无意义的。那么我们就可以只声明 buyBook() 方法,而不需要定义 buyBook() 的方法体,这种没有方法体的方法叫作抽象方法。

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

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

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

在具体执行时,给父类赋予不同的子类就会实现不同的功能,这就是多态的意义,即抽象类和抽象方法的作用。在实际开发中我们需要创建抽象类的子类来完成开发,例如:
  1. public abstract class Member {
  2. public abstract void buyBook();
  3. }
  4.  
  5. public class OrdinaryMember extends Member {
  6. //实现父类的抽象方法
  7. @Override
  8. public void buyBook() {
  9. // TODO Auto-generated method stub
  10. System.out.println("普通会员买书打9折");
  11. }
  12. }
  13.  
  14. public class SuperMember extends Member{
  15. //实现父类的抽象方法
  16. @Override
  17. public void buyBook() {
  18. // TODO Auto-generated method stub
  19. System.out.println("超级会员买书打6折");
  20. }
  21. }
  22.  
  23. public class Test {
  24. public static void main(String[] args) {
  25. Member member = new OrdinaryMember();
  26. member.buyBook();
  27. member = new SuperMember();
  28. member.buyBook();
  29. }
  30. }
运行结果为:

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

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


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

相关文章