多态
多态的概念
多态的本质是:一个程序中同名的不同方法。在面向对象的程序设计中,多态主要有以下三种方式来实现。
- 通过子类对父类方法的覆盖来实现多态
- 通过一个类中方法的重载来实现多态
- 通过将子类的对象作为父类的对象实现多态。
覆盖的概念我们在前面以及介绍了,接下来我们简单阐述下何为重载。
- 重载是指一个类里面(包括父类的方法)存在方法名相同,但是参数不一样的方法,参数不一样可以是不同的参数个数、类型或顺序
- 如果仅仅是修饰符、返回值、throw的异常 不同,那么这是2个相同的方法
我们重点阐述第三种实现方法,即通过将子类的对象作为父类的对象实现多态
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。(这句话是我理解第三种方法的关键,请仔细阅读理解)
继承是面向对象语言中一个代码复用的机制,简单说就是子类继承了父类中的非私有属性和可以继承的方法,然后子类可以继续扩展自己的属性及方法。
对象的引用型变量是具有多态性的,因为一个引用型变量可以指向不同形式的对象,即:子类对象作为父类对象来使用。在这里涉及到了向上转型和向下转型。
向上转型
子类对象转为父类,父类可以是接口。公式:Father f = new Son(); Father是父类或接口,Son是子类。
向下转型
父类对象转为子类。公式:Son s = (Son) f;
在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型。若Father f = new Father()那么不可以转换,运行会报错。
对于多态,我们先举一个例子。在一个单位中,有职工employee,职工中又有少数是管理者manager,管理者中又有一部分是领导。若小明是管理者manager类的对象,他也可以被看做是employee的对象,即他也可以被看做是一个职工,他同时具备着职工的所有属性。
//定义一个父类
class Employee
{
String name;
int age;
float salary;
Employee(){};
Employee(String name,int age,float sal)
{
this.name=name;
this.age=age;
this.salary=sal;
}
String getInfo()
{
return "职工姓名:"+name+"年龄:"+age+"工资:"+salary;
}
//定义一个子类
class Manager extends Employee
{
float allowance;
Manager(String name,int age,float sal,float aa)
{
this.name=name;
this.age=age;
this.salary=sal;
allowance=aa;
}
//测试
public static void main(String[] args) {
Employee emp1=new Employee("小明",23, 1000); //emp1是Employee的对象
System.out.println(emp1.getInfo());
Employee emp2=new Manager("小明",23, 1000,5000); //注意此处emp2是Manager类的对象
System.out.println(emp2.getInfo());
}
接下来我们再来看一个经典的多态讲解案例,代码如下:
class A {
public String show(D d) {
return ("A and D");
}
public String show(A a) {
return ("A and A");
}
class B extends A {
public String show(B b) {
return ("B and B");
}
public String show(A a) {
return ("B and A");
}
}
class C extends B {}
class D extends B {}
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("--------------");
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("--------------");
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
这段代码的输出是啥呢?大家可以先分析分析,然后再看正确答案。
在分析结果之前,我们先来看相关的知识点概念:
- 当父类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
产生多态时候,各个方法调用的优先级顺序由高到低依次为:
this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)
我们来分析以下结果:
(1)System.out.println(“1–” + a1.show(b));
首先a1是一个标准的A对象的引用变量,传入的参数为标准的 new B(),我们在A类的方法中找不到show(B b)的方法,接着
去看A的超类是否有show(B b)的方法,发现A没有超类。那么去执行this.show((super)O),即A类中的show(A a)方
法,所以输出为A and A
(2)System.out.println(“2–” + a1.show©);
同理,a1.show(c) C的父类是B,所以C也是A的子类,那么最后还是调用A类中的show(A a)方法,所以输出为A and A
(3)System.out.println(“3–” + a1.show(d));
这个没什么好疑惑的,直接就是调用A类中的show(D obj),所以输出为A and D
(4)System.out.println(“4–” + a2.show(b));
这个的结果输出就有点迷惑人了,小伙伴们会惊奇,为什么不是调用B类中的show(B b)方法输出B and B ?我们来解释
下,a2是一个父类A的引用变量指向了一个子类B的对象,也就是说表面类型是A,实际类型是B。当我们调用方法
的时候,首先从其表面类型里边寻找方法 show(B b)结果没有找到,那么按照调用优先级,我们最终会调用
到this.show((super)O) 也就是说我们调用了A类中的show(A a)方法。按照上边所述的概念:当父类对象引用变量
引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必
须是在超类中定义过的,也就是说被子类覆盖的方法。在当前的表面类型中找到了该方法show(A a),并且在子类(B)中
也覆盖了该方法,那么最终会调用实际类型(B类)中的该方法show(A a),所以输出B and A。
(5)System.out.println(“5–” + a2.show©);
这个分析和4中的保持一致
(6)System.out.println(“6–” + a2.show(d));
这个没什么疑问,在A类中找到了该方法show(D d),并且子类没有覆盖该方法,所以直接调用A类中的方法,输出A and D
(7)System.out.println(“7–” + b.show(b));
b.show(b)应该没什么疑问,会直接调用B类中的方法show(B b) 输出 B and B
(8)System.out.println(“8–” + b.show©);
根据方法调用优先级,最终会调用到B类中的方法show(B b) 输出 B and B
(9)System.out.println(“9–” + b.show(d));
根据方法调用优先级,最终会调用到A类中的方法show(D d) 输出 A and D
这是多态学习中的一个经典案例,我们要牢记方法之间的调用优先级,并且要分清楚表面类型和实际类型,
以及区分子类中是否进行了方法的覆盖等知识点,这样才能做到条理清晰。