Java 多态的一道例题
一、题目
class A {
public String show(D obj) {
return "A and D";
}
public String show(A obj) {
return "A and A";
}
}
class B extends A {
public String show(B obj) {
return "B and B";
}
public String show(A obj) {
return "B and A";
}
}
class C extends B {}
class D extends B {}
public class PolymorphicTest {
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("4"+a2.show(b));
System.out.println("5"+a2.show(c));
System.out.println("6"+a2.show(d));
System.out.println("7"+b.show(b));
System.out.println("8"+b.show(c));
System.out.println("9"+b.show(d));
System.out.println("10"+d.show(a1));
System.out.println("11"+d.show(a2));
System.out.println("11"+d.show(b));
System.out.println("12"+d.show(c));
}
}
二、运行结果
三、分析
这个题目主要考察重写、重载、继承、多态(向上转型)中方法的访问规则
1、分析一下ABCD四个类的继承关系,及AB中show方法的关系
(1)ABCD四个类的继承关系:
B是A的子类,C和D又是B的子类
(2)AB中show方法的关系
重载关系,存在于同类中的同名不同参数列表的方法之间、父类和子类的同名不同参数列的方法之间
A中的show D、show A 与 B中的show B
A中的show D 与 B中的show B、show A
构成重载关系的几个方法之间,其实没什么关系,只是一个方法名下包括了几种不同的参数列表,自动根据传入的参数选择合适的方法。
重载关系的几个方法是可以共存的,解题时完全可以将几个重载关系的方法换成不同的名字方便区分。
重写关系,存在于父类和子类的同名同参的方法之间
A中的show A 与 B中的show A
2、总结一下单一情况下的方法调用顺序
先说子类调用方法时,先调用子类的方法,后调用从父类继承来的方法。
然后是向上转型,只能调用父类的方法,不能调用子类特有的方法。遇到重写的方法,先调用子类重写后的,后调用父类重写前的。
然后是向下转型,同子类调用方法。
如果方法名匹配上了,几个重载的方法先调用谁,看调用者的编译类型和重载方法的形参列表的匹配程度,能直接匹配的先调用,然后是调用者的编译类型向上转型后和重载方法的形参列表能直接匹配的再调用(只针对单参数的情况)
四、总结
1、经过向上转型的对象访问父类和子类的方法时的顺序
先 子类方法 后 父类方法,先 实参直接匹配形参 后 实参向上转型匹配形参。
向上转型的对象中只能访问子类中重写的方法,非重写的方法无法访问。
如果子类中有重写的方法,并且这个向上转型的对象调用的方法也存在实参向上转型匹配形参的情况,
访问顺序由先到后如下:
子类中经过重写且形参直接匹配实参的方法,(子类中重载的方法不可被向上转型的对象调用)
然后父类中形参直接匹配实参的方法(如果父类中有多个重载的方法,看实参和形参的匹配程度,完全一致 > 自动转换 > 强制转换)
再然后子类中重写且实参向上转型后可以匹配形参的方法,(子类中重载的方法不可被向上转型的对象调用)
最后父类中实参向上转型匹配形参的方法。(如果父类中有多个重载的方法,看实参和形参的匹配程度,完全一致 > 自动转换 > 强制转换)
注1:这里的子类和父类并不单单指两个类,可以是一长串互为子类父类的类,访问顺序就是先子类,后父类,后父类的父类……直到Object类
注2:这一套走下来没有符合的方法,则报错。
2、继承的访问规则
先子类后父类,
先子类直接匹配,后父类直接匹配。
五、具体分析
以序号4的输出为例,
System.out.println("4"+a2.show(b));
a2为向上转型,a2的编译类型是A,运行类型是B。b作为实参,类型为B,可能直接匹配上实参类型,也可能向上转型成A。
首先,最容易忽略的点,即使不考虑实参b,a2这个向上转型也不能访问子类中的重载方法,也就是B中的show B。
然后,访问顺序如下,
最先看的是,先子类、重写、实参直接匹配形参,B的show A,无法直接匹配,
然后是,父类、实参直接匹配形参,A的show A和show D,因为b的类型为B,也无法直接匹配
再然后是,子类、重写、实参向上转型匹配形参,B的show A,看一下能不能b向上转型成A,可以,就这个方法了,输出4B and A。
六、后续补充
在涉及到多态的内容中,应该将子类和父类的重写的方法视为毫无关系的两个方法。
题中的访问包括对象的向上转型和参数的向上转型。
顺序为:
1、子类中、重写的、参数类型直接匹配的方法
2、父类中、 参数类型直接匹配的方法
3、子类中、重写的、形参为实参父类的方法
4、父类中、 形参为实参父类的方法
(注意,这个顺序跟那些方法是重载的完全没关系,不要将重载和重写搞混了)
5、未找到合适方法,报错
(子类中对父类某方法进行重载的方法,是子类特有的方法,向上转型的对象不能调用子类特有方法!!!!)
比较明显能体现上述顺序的例子有:
T4,a2.show(b)
调用顺序为
1、无 应该是B.show(B),但没有
2、无 应该是A.show(B),但没有
3、B.show(A) √
4、A.show(A)
T6,a2.show(d)
调用顺序为
1、无 应该是B.show(D),但没有
2、A.show(D) √
3、B.show(B) 然后是 B.show(A)
4、A.show(B) 然后是 A.show(A)
T9,b.show(d)
调用顺序为
1、无 应该是B.show(D),但没有
2、A.show(D) √
3、B.show(B) 然后是 B.show(A)
4、A.show(B) 然后是 A.show(A)
T11,d.show(a2)
调用顺序为
1、无 应该是D.show(A),但没有
2、B.show(A) 然后是A.show(A)
3、无 应该是D.show(A的父类),但没有
4、无 应该是B.show(A的父类) 然后是A.show(A的父类),但没有
5、没有合适的方法,报错
注意,B.show(B)不会被调用,因为a2的编译类型为A,而show(B)的形参类型是B,无法通过编译,
如果要通过,需要a2向下转型强制转换成B。