多态性有两种:
1)编译时多态性
对于多个同名方法,如果在编译时能够确定执行同名方法中的哪一个,则称为编译时多态性.
2)运行时多态性
如果在编译时不能确定,只能在运行时才能确定执行多个同名方法中的哪一个,则称为运行时多态性.
方法覆盖表现出两种多态性,当对象获得本类实例时,为编译时多态性,否则为运行时多态性,例如:
XXXX x1 = new XXXX(参数列表); //对象获得本类实例,对象与其引用的实例类型一致
XXX xx1 = new XXX(参数列表);
x1.toString(); //编译时多态性,执行XXX类的方法.
xx1.toString(); //编译时多态性,执行XXXX类覆盖的方法.
XXXX为XXX的父类.
由于子类对象既是父类对象,父类对象与子类对象之间具有赋值相容性,父类对象能够被赋值为子类对象.例如,
XXXX x2 = new XXX(参数列表); //父类对象获得子类实例,子类对象即是父类对象
x2.toString(); //运行时多态
x2声明为父类对象却获得子类XXX的实例,那么x2.toString()究竟执行父类方法还是执行子类覆盖的方法呢?
这分为两种情况:
取决于子类是否覆盖父类方法.如果子类覆盖父类方法,则执行子类方法;
如果没有覆盖,则执行父类方法.
在编译时,仅仅依据对象所属的类,系统无法确定到底应该执行那个类的方法,只有运行时才能确定,因此这是运行时多态.
父类对象并不能执行所有的子类方法,只能执行那些父类中声明\子类覆盖的子类方法.
java多态实现
java的多态和c++一样,是通过动态绑定或者说运行时绑定来实现的。当调用某一个对象引用的方法时,因为编译器并不知道这个引用到底指向的是变量声明时说明的类型对象,还是该类型子类的对象。因此编译器无法为这次调用绑定到具体的某个方法。只有通过java中运行时类型识别(RTT)在运行时绑定到具体的方法
方法的重写overriding和方法的重载overloading是java多态的不同表现。重写overriding是父类和子类之间多态性的一种表现,重载overloading是一个类中多态性的表现。
给出一个具体例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class People { public String toString() { return "I am a people!" ; } public void eat() { }; public void speak() { }; } class Boy extends People { public String toString() { return "I am a boy!" ; } public void fight() { }; public void speak() { }; } class Girl extends People { public String toString() { return "I am a girl!" ; } public void sing() { }; public void speak() { }; } public class TestToString { public static void main(String args[]) { People p = new Girl(); System.out.println(p.toString()); } } |
运行结果是:
1
|
I am a girl! |
p是People的一个引用,但是在运行时因为是Girl对象,所以还是调用了Girl的toString方法
深入理解java多态
声明,这里借鉴了其他同学的例子,原文链接:http://blog.csdn.net/thinkghoster/article/details/2307001
测试题目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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 Main { 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(a1.show(b)); // 1 System.out.println(a1.show(c)); // 2 System.out.println(a1.show(d)); // 3 System.out.println(a2.show(b)); // 4 System.out.println(a2.show(c)); // 5 System.out.println(a2.show(d)); // 6 System.out.println(b.show(b)); // 7 System.out.println(b.show(c)); // 8 System.out.println(b.show(d)); // 9 } } |
答案
1
2
3
4
5
6
7
8
9
|
A and A A and A A and D B and A B and A A and D B and B B and B A and D |
解析
我开始做这道题目,4、5、6、9全部做错了,原因就是没能很好的理解java的多态性,这里说明一下
首先,要深刻的理解重写和重载,重写不仅仅包括了函数名称相同,也包括参数类型和返回值类型
其次,深刻理解这句话“当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类重写的方法”
然后,我们在来分析一下这几道题目
问题:你认为B重写了父类A的show方法了吗?如果重写了,重写了几个?
答案:重写了,重写了一个,也就是public String show(A obj),为什么public String show(B obj)不算重写父类方法呢,很简单,因为参数类型不同
举例分析
看了上面的分析,我们也来分析两个例子:
一、a2.show(b):
a2是一个引用变量,类型为A,b是B的一个实例。首先,在类A中找show(B obj),没有找到。于是到A的超类中找,而A没有超类,因此转向了A.this((super)B),(super)B为A,因此在A中找到了show(A obj)的方法,但是由于a2引用的类B的一个对象,B重写了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为“B and A”
二、a2.show(c):
a2是一个引用变量,类型为A,b是B的一个实例。首先,在类A中找show(C obj),没有找到。于是到A的超类中找,而A没有超类,因此转向了A.this((super)C),(super)C为B,到这里为止,这个a2.show(c)变成了a2.show(b)的问题,而a2.show(b)上面已经分析了是输出"B and A",因此这里也是输出“B and A”