这是面试过程中常常被问到的,那么今天我们就来从一个完全不懂得的状态到一步步了解什么是多态,来深入掌握多态的概念。
什么是多态?多态:一个父类和多个子类,即父类引用指向子类对象。在调用一个方法时,从源代码上看,无法确定调用了哪个对象的方法(因为父子类有相同的方法),只有在程序运行期间根据对象变量引用的实际对象才能确定此方法是哪个对象的,这种现象称之为动态绑定。一句话概括就是:事物在运行过程中存在不同的状态。
多态是怎样实现的?继承:多个子类对同一方法的重写
接口:实现接口并覆盖接口中的同一方法
多态实现的三个必要条件
- 要有继承关系;
- 子类要重写父类的方法;
- 父类引用指向子类对象。
图片源于网络,侵删
举个例子
首先我们定义几个类,一个父类Person,以及几个子类Student、Teacher、Doctor。
package com.atguigu.polymorphic;
class Person {
public void print() {
System.out.println("父类需改进方法");
}
public void fun() {
System.out.println("父类中其他方法");
}
//行为
public void eat() {
System.out.println("人在吃饭!");
}
public static void look() {
System.out.println("人在看东西!");
}
public void move() {
System.out.println("人在移动!");
}
}
class Student extends Person {
@Override
public void print() {
System.out.println("Student类改进后的方法");
}
public void eat() {
System.out.println("学生在吃面包!");
}
public static void look() {
System.out.println("学生在看书!");
}
public void play() {
System.out.println("学生在进行课外活动!");
}
}
class Teacher extends Person {
@Override
public void print() {
System.out.println("Teacher类改进后的方法");
}
}
class Doctor extends Person {
@Override
public void print() {
System.out.println("Doctor类改进后的方法");
}
}
然后我们创建测试类
package com.atguigu.polymorphic;
public class Test_1008 {
public static void main(String[] args) {
System.out.println("=====重写测试=====");
Person per = new Student();//向上转型,子类对象给了父类的引用
per.print();//由于该方法被覆写,那么调用的是被覆写后的方法
per.fun();//调用父类的fun方法
System.out.println("=====多态测试=====");
Person per1 = new Student();
Person per2 = new Teacher();
Person per3 = new Docter();
per1.print();//由于该方法被覆写,那么调用的是被覆写后的方法
per2.print();
per3.print();
System.out.println("=====行为测试=====");
Person person = new Student();
person.eat();
person.look();
person.move();
}
}
测试结果如下:
=====重写测试=====
Student类改进后的方法
父类中其他方法
=====多态测试=====
Student类改进后的方法
Teacher类改进后的方法
Doctor类改进后的方法
=====行为测试=====
学生在吃面包!
人在看东西!
人在移动!
在以上代码中,Student、Teacher、Doctor类继承了Person类,其中Student子类重写(override)了父类的两个成员方法eat(),look()。其中eat()是非静态的,look()是静态的(static)。并且在测试类Test_1008中 Person person= new Student();语句在堆内存中开辟了子类(Student)的对象,并把栈内存中的父类(Person)的引用指向了这个Student对象。从而满足了java多态的的必要三个前提。
由测试结果可以看出来
子类Student重写了父类Person的非静态成员方法person.eat();的输出结果为:学生在吃面包!
子类重写了父类(Person)的静态成员方法person.look();的输出结果为:人在看东西!
未被子类(Student)重写的父类(Person)方法person.play()输出结果为:人在移动!
在实际开发工作中,常常遇到一个功能有多种实现方式,比如支付方式,有分微信支付、京东支付、支付宝、银联等支付方式,不同支付方式的大概流程大抵相似,实现细节有所区别。这个时候就可以用到java的多态机制,先定义一个公共接口,接口定义支付流程的各个方法,具体的支付方式实现该接口的方法。在控制层,利用spring的注入获取支付类型和支付方式实现类的引用映射,根据请求需要的支付类型就可以调用对应支付方式的方法,以此实现业务的解耦和拓展。后期需要增加支付方式,只需要实现共同接口即可。
PaymentTypeService.java
/**
* 支付方式接口
*/
public interface PaymentTypeService {
public String type();
public void methodA();
public void methodB();
}
实现A:APaymentTypeServiceImpl.java
/**
* 支付方式A实现类
*/
@Service
public class APaymentTypeServiceImpl implements PaymentTypeService {
private final String type = "A";
@Override
public void methodA() {
// TODO Auto-generated method stub
System.out.println("PaymentType A invoke methodA");
}
@Override
public void methodB() {
// TODO Auto-generated method stub
System.out.println("PaymentType A invoke methodB");
}
@Override
public String type() {
return type;
}
}
实现B:BPaymentTypeServiceImpl.java
/**
* 支付方式B实现类
*/
@Service
public class BPaymentTypeServiceImpl implements PaymentTypeService {
private final String type = "B";
@Override
public void methodA() {
// TODO Auto-generated method stub
System.out.println("PaymentType B invoke methodA");
}
@Override
public void methodB() {
// TODO Auto-generated method stub
System.out.println("PaymentType B invoke methodB");
}
@Override
public String type() {
return type;
}
}
实际引用: DemoController.java
@RestController
public class DemoController {
private Map<String, PaymentTypeService> paymentTypeServices;
/**
* 构造函数初始化不同支付方式类型和实现类引用map
* @param services
*/
public DemoController(@Autowired List<PaymentTypeService> services){
paymentTypeServices = services.stream().collect(Collectors.toMap(PaymentTypeService::type, i->i));
}
/**
* 请求某个支付方式
* @date: 2018年4月23日 下午2:21:28
* @param type
*/
@GetMapping("/test/{type}")
public void test(@PathVariable("type") String type){
// 获取该支付方式实现类
PaymentTypeService service = paymentTypeServices.get(type);
service.methodA();
service.methodB();
}
}
那么我们可以根据以上情况总结出多态成员访问的特点:
- 成员变量:编译看左边,运行看左边。
- 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
- 成员方法:编译看左边,运行看右边。(方法重写的意义)
- 静态方法:编译看左边,运行看左边。静态和类相关,算不上重写,所以访问还是左边的。只有非静态的成员方法,编译看左边,运行看右边。
可替换性:多态对已存在的代码具有可替换性
可扩充性:增加新的子类并不影响已存在类的多态性、继承性以及其他特性的运行和操作
接口性:多态是父类(超类)通过方法签名,向子类提供了共同的接口,子类可以通过覆写完善或者覆盖这个接口。
灵活性:在应用中体现了灵活多样的操作,提高了使用效率。
多态的使用场景多态的实现依赖于继承,先声明一个父类的实例,再于合适之时给它分别赋予不同的子类实例,此后操作该实例就仿佛操作子类的实例一般。就好比一个退了伍的军人去当了厨师,他的首先是一个人,当有召时穿上军服就是一名军人,然后在工作是就是一名厨师。这个现象便是多态特性的一个实际运用,所谓多态,意思是有多种状态。
引入多态概念的好处是,只要某些类型都从同一个父类派生而来,就能在方法内部把它们当作同一种类型来处理,而无需区分具体的类型。仍以人的不同职业为例,不管是学生、医生还是教师,都是某种职业,于是完全可以定义一个相似方法,根据输入的不同职业的参数,让这这个职业自己去进行相应的操作。
总结定义方法参数列表时、定义方法返回值类型时、定义类的成员变量时、定义数组元素类型时,都定义为父类类型,这样就可以传递、返回、赋值、装任意子类类型的对象定义时定义父类类型使用时使用子类类型的对象