什么是多态,Java多态详解
面向对象有三大特征:封装、继承和多态,在前面的章节中我们已经学习了封装和继承,本节我们来学习多态。
多态的概念本身是比较抽象的,简单解释就是一个事物有多种表现形态,具体到 Java 程序中,就是定义一个方法,在具体的生产环境中根据不同的需求呈现出不同的业务逻辑,我们通过下面这个例子来理解什么是多态。
假设一个业务场景,书店的普通会员买书,收银员为该会员计算优惠折扣,思路是:先创建普通会员类,定义买书方法描述该会员买书的优惠折扣,再创建收银员类,将普通会员对象作为成员变量,在结算方法中调用普通会员的买书方法,代码所下:
使用多态可以进行优化,多态的思路是:创建 Member 类,作为 OrdinaryMember 和 SuperMember 的父类,在 OrdinaryMember 和 SuperMember 中分别对父类方法进行重写,在 Cashier 类中定义 Member 类型的成员变量,代码如下所示:
这就是多态,从 main 方法的角度来看这段代码,我们不去定义具体的 OrdinaryMember 或者 SuperMember,而是定义 Member,然后将具体的实例化对象赋给 Member,即同一个 Member 有多种表现形式。
在实际开发中,多态主要有两种表现形式:一种是定义方法时形参为父类,调用方法时传入的参数为子类对象;另一种是定义方法时返回值的数据类型为父类,调用方法时返回子类对象。
定义方法时形参为父类,调用方法时传入的参数为子类对象,例如:
定义方法时返回值的数据类型为父类,调用方法时返回子类对象,例如:
在现实世界中也是合理的,比如张三是普通会员,我们说张三是会员也是没有错的。但是反过来就行不通了,如果我们说会员是张三,这句话从逻辑上来讲是行不通的。因为如果李四办理了超级会员,那么李四也是会员,同理王五也可以是会员,所以直接说会员是张三就以偏概全了。既然在现实世界中行不通,那么在程序的世界中也是行不通的,即我们不能把一个子类引用指向父类对象,如图 1 所示。
图 1 不能把一个子类引用指向父类对象
这是一种数据类型错误,例如不能把 Member 转为 OrdinaryMember。
但是如果我们一定要将 Member 转为 OrdinaryMember 呢?我一定要说会员就是张三怎么办?好吧,既然你这么强势,那就随你吧!强制性地把 Member 转为 OrdinaryMember,这种方式叫作强制类型转换,需要在目标对象前加括号,括号内注明强制转换之后的数据类型,代码如下:
声明抽象方法时需要添加 abstract 关键字,例如:
既然抽象类不能被实例化,抽象方法也没有方法体,那么为什么要创建抽象类和抽象方法呢?到底有什么用呢?
抽象类和抽象方法需要结合多态来使用,我们知道构建多态的基础是类的继承和方法重写。既然有重写就意味着父类方法只需要声明,不需要具体的实现,具体实现由子类来完成,父类只是一个抽象的概念或者模板。那么我们就可以把父类定义为抽象类,需要被子类重写的方法定义为抽象方法,并针对这个抽象的概念进行编程。
在具体执行时,给父类赋予不同的子类就会实现不同的功能,这就是多态的意义,即抽象类和抽象方法的作用。在实际开发中我们需要创建抽象类的子类来完成开发,例如:
如果子类也是抽象类,可以不用重写父类的抽象方法,如下图所示:
多态的概念本身是比较抽象的,简单解释就是一个事物有多种表现形态,具体到 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折
如果子类也是抽象类,可以不用重写父类的抽象方法,如下图所示: