1.继承条件下的构造方法调用。
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
|
class
Grandparent
{
public
Grandparent()
{
System.out.println(
"GrandParent Created."
);
}
public
Grandparent(String string)
{
System.out.println(
"GrandParent Created.String:"
+ string);
}
}
class
Parent
extends
Grandparent
{
public
Parent()
{
//super("Hello.Grandparent.");①
System.out.println(
"Parent Created"
);
//super("Hello.Grandparent.");②
}
}
class
Child
extends
Parent
{
public
Child()
{
System.out.println(
"Child Created"
);
}
}
public
class
TestInherits
{
public
static
void
main(String args[])
{
Child c =
new
Child();
}
}
|
① 结果:
② 结果:
结论:通过super调用基类构造方法,必须是子类构造方法中的第一句。
2.为什么子类构造方法运行之前,必须调用父类的构造方法?
构造方法的主要作用是初始化,如果子类先运行,没有初始化,会出错。
3.使用javap –c命令反编译。
1
2
3
4
5
6
7
8
9
|
public
class
ExplorationJDKSource {
/**
* @param args
*/
public
static
void
main(String[] args) {
System.out.println(
new
A());
}
}
class
A{}
|
结果:
反编译:
分析:main方法实际调用了public void println(Object x),这一方法内部调用了String类的valueOf()方法。
valueOf方法又调用了Object.toString方法:
public String toString(){
return getClass().getName()+”@”+
Integer.toHexString(hashCode());
}
结论:为明确继承的类,都继承类Object。
4.子类,父类的覆盖关系
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
Fruit
{
public
String toString()
{
return
"Fruit toString."
;
}
public
static
void
main(String args[])
{
Fruit f=
new
Fruit();
System.out.println(
"f="
+f);①
// System.out.println("f="+f.toString());②
}
}
|
① 结果:
② 结果:
分析:
Fruit类覆盖类Object类的toString方法。
“+”运算任何一个对象与一个String对象连接时会隐式调用toString()方法。
默认情况下方法返回“类名@+方法hashCode”。为了返回有意义的信息,子类可以重写toString()方法.所以两次结果为:f = Fruit toString。
5.调用父类被覆盖的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class
Hello{
public
void
printTest(){
System.out.println(
"Hello!"
);
}
}
class
Hi
extends
Hello{
public
void
printTest(){
System.out.println(
"Hi!"
);
}
public
void
test(){
super
.printTest();
//使用super调用父类方法
printTest();
}
}
public
class
Test{
public
static
void
main(String[] args){
Hi h =
new
Hi();
h.test();
}
}
|
结果:
6.类型转换,运行代码
class Mammal{}
class Dog extends Mammal {}
class Cat extends Mammal{}
public class TestCast
{
public static void main(String args[])
{
Mammal m;
Dog d=new Dog();
Cat c=new Cat();
m=d;
d=m;//会报错
d=(Dog)m;
d=c;//会报错
c=(Cat)m;
}
}
结果:
7.运行代码思考问题
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
|
class
Parent{
public
int
myValue=
100
;
public
void
printValue() {
System.out.println(
"Parent.printValue(),myValue="
+myValue);
}
}
class
Child
extends
Parent{
public
int
myValue=
200
;
public
void
printValue() {
System.out.println(
"Child.printValue(),myValue="
+myValue);
}
}
猜想以下代码运行结果:
public
class
ParentChildTest {
public
static
void
main(String[] args) {
Parent parent=
new
Parent();
parent.printValue();
Child child=
new
Child();
child.printValue();
parent=child;
parent.printValue();
parent.myValue++;
parent.printValue();
((Child)parent).myValue++;
parent.printValue();
}
}
|
猜想:100 200 200 201 202
结果:
分析:
Parent parent=new Parent();
parent.printValue(); 调用了父类方法输出为100
Child child=new Child();
child.printValue();调用子类方法输出为200
parent=child;子类赋给父类
parent.printValue();输出200
parent.myValue++;父类的myValue+1 为101
parent.printValue();这里输出的是子类赋给父类的方法输出200
((Child)parent).myValue++;父类强转为子类+1 为201
parent.printValue();输出201
总结:
① 当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己的“真实”类型所决定,这就是说:对象是子类型的,它就调用子类型的方法,是父类型的,它就调用父类型的方法。
② 如果子类与父类有相同的字段,则子类中的字段会代替或隐藏父类的字段,子类方法中访问的是子类中的字段(而不是父类中的字段)。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它。
③ 如果子类被当作父类使用,则通过子类访问的字段是父类的。
8.多态代码生成字节指令
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
|
class
Parent
{
public
int
value=
100
;
public
void
Introduce()
{
System.out.println(
"I'm father"
);
}
}
class
Son
extends
Parent
{
public
int
value=
101
;
public
void
Introduce()
{
System.out.println(
"I'm son"
);
}
}
class
Daughter
extends
Parent
{
public
int
value=
102
;
public
void
Introduce()
{
System.out.println(
"I'm daughter"
);
}
}
public
class
TestPolymorphism
{
public
static
void
main(String args[])
{
Parent p=
new
Parent();
p.Introduce();
System.out.println(p.value);
p=
new
Son();
p.Introduce();
System.out.println(p.value);
p=
new
Daughter();
p.Introduce();
System.out.println(p.value);
}
}
|
多态实现:
JAVA使用了后期绑定的概念。当向对象发送消息时,在编译阶段,编译器只保证被调用方法的存在,并对调用参数和返回类型进行检查,但是并不知道将被执行的确切代码,被调用的代码直到运行时才能确定。
将一个方法调用同一个方法主体关联起来被称作绑定,JAVA中分为前期绑定和后期绑定(动态绑定或运行时绑定),在程序执行之前进行绑定(由编译器和连接程序实现)叫做前期绑定,因为在编译阶段被调用方法的直接地址就已经存储在方法所属类的常量池中了,程序执行时直接调用,具体解释请看最后参考资料地址。后期绑定含义就是在程序运行时根据对象的类型进行绑定,想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而找到对应的方法,简言之就是必须在对象中安置某种“类型信”,JAVA中除了static方法、final方法(private方法属于)之外,其他的方法都是后期绑定。后期绑定会涉及到JVM管理下的一个重要的数据结构——方法表,方法表以数组的形式记录当前类及其所有父类的可见方法字节码在内存中的直接地址。
动态绑定具体的调用过程为:
1.首先会找到被调用方法所属类的全限定名
2.在此类的方法表中寻找被调用方法,如果找到,会将方法表中此方法的索引项记录到常量池中(这个过程叫常量池解析),如果没有,编译失败。
3.根据具体实例化的对象找到方法区中此对象的方法表,再找到方法表中的被调用方法,最后通过直接地址找到字节码所在的内存空间。
9.多态含义和用途
让我们看一个开发场景:
某动物园有一饲养员小李,
每天需要给他所负责饲养的狮子、猴子和鸽子喂食。
请用一个程序来模拟他喂食的过程。
①三种动物对应三个类,每个类定义一个eat()方法,表示吃饲养员给它们的食物。
再设计一个Feeder类代表饲养员,其name字段保存饲养员名字,三个方法分别代表喂养三种不同的动物,其参数分别引用三种动物对象。
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
48
49
50
51
52
53
54
55
56
57
58
59
60
|
public
class
Zoo
{
public
static
void
main(String args[])
{
Feeder f =
new
Feeder(
"小李"
);
// 饲养员小李喂养一只狮子
f.feedLion(
new
Lion());
// 饲养员小李喂养十只猴子
for
(
int
i =
0
; i <
10
; i++)
{
f.feedMonkey(
new
Monkey());
}
// 饲养员小李喂养5只鸽子
for
(
int
i =
0
; i <
5
; i++)
{
f.feedPigeon(
new
Pigeon());
}
}
}
class
Feeder
{
public
String name;
public
Feeder(String name)
{
this
.name = name;
}
public
void
feedLion(Lion l)
{
l.eat();
}
public
void
feedPigeon(Pigeon p)
{
p.eat();
}
public
void
feedMonkey(Monkey m)
{
m.eat();
}
}
class
Lion
{
public
void
eat()
{
System.out.println(
"我不吃肉谁敢吃肉!"
);
}
}
class
Monkey
{
public
void
eat()
{
System.out.println(
"我什么都吃,尤其喜欢香蕉。"
);
}
}
class
Pigeon
{
public
void
eat()
{
System.out.println(
"我要减肥,所以每天只吃一点大米。"
);
}
}
|
这种编程方式有什么不合理的地方?
每次喂食都要创建一次类。重复步骤多。
①引入继承
定义一个抽象基类Animal,其中定义一个抽象方法eat(),三个子类实现这个抽象方法。
Feeder类的三个喂养方法现在可以合并为一个feedAnimal()方法,注意它接收一个类型为Animal参数,而不是三个具体的动物类型。
依据多态特性,此方法将可以接收任何一个派生自Animal类的子类对象
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
48
49
50
51
52
53
54
55
56
|
public
class
Zoo
{
public
static
void
main(String args[])
{
Feeder f =
new
Feeder(
"小李"
);
//饲养员小李喂养一只狮子
f.feedAnimal(
new
Lion());
//饲养员小李喂养十只猴子
for
(
int
i =
0
; i <
10
; i++)
{
f.feedAnimal(
new
Monkey());
}
//饲养员小李喂养5只鸽子
for
(
int
i =
0
; i <
5
; i++)
{
f.feedAnimal(
new
Pigeon());
}
}
}
class
Feeder
{
public
String name;
Feeder(String name)
{
this
.name = name;
}
public
void
feedAnimal(Animal an)
{
an.eat();
}
}
abstract
class
Animal
{
public
abstract
void
eat();
}
class
Lion
extends
Animal
{
public
void
eat()
{
System.out.println(
"我不吃肉谁敢吃肉!"
);
}
}
class
Monkey
extends
Animal
{
public
void
eat()
{
System.out.println(
"我什么都吃,尤其喜欢香蕉。"
);
}
}
class
Pigeon
extends
Animal
{
public
void
eat()
{
System.out.println(
"我要减肥,所以每天只吃一点大米。"
);
}
}
|
①进一步优化喂养一群动物
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
|
package
zoo3;
public
class
Zoo
public
static
void
main(String args[]) {
Feeder f =
new
Feeder(
"小李"
);
Animal[] ans =
new
Animal[
16
];
//饲养员小李喂养一只狮子
ans[
0
] =
new
Lion();
//饲养员小李喂养十只猴子
for
(
int
i =
0
; i <
10
; i++) {
ans[
1
+ i] =
new
Monkey();
}
//饲养员小李喂养5只鸽子
for
(
int
i =
0
; i <
5
; i++) {
ans[
11
+ i] =
new
Pigeon();
}
f.feedAnimals(ans);
}
}
class
Feeder {
public
String name;
Feeder(String name) {
this
.name = name;
}
public
void
feedAnimals(Animal[] ans) {
for
(Animal an : ans) {
an.eat();
}
}
}
abstract
class
Animal {
public
abstract
void
eat();
}
class
Lion
extends
Animal {
public
void
eat() {
System.out.println(
"我不吃肉谁敢吃肉!"
);
}
}
class
Monkey
extends
Animal {
public
void
eat() {
System.out.println(
"我什么都吃,尤其喜欢香蕉。"
);
}
}
class
Pigeon
extends
Animal {
public
void
eat() {
System.out.println(
"我要减肥,所以每天只吃一点大米。"
);
}
}
|
④第二次重构之后,Feeder类的feedAnimals()方法接收的是一个Animal数组,这有一个限制,就是只能创建固定个数的数组,无法动态地增减动物个数。
想想以下场景:
(1)动物园新进了一些动物
(2)某动物生病不幸死亡
(3)……
我们的代码能否应付以上的场景?
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
48
|
import
java.util.Vector;
public
class
Zoo {
public
static
void
main(String args[]) {
Feeder f =
new
Feeder(
"小李"
);
Vector<Animal> ans =
new
Vector<Animal>();
//饲养员小李喂养一只狮子
ans.add(
new
Lion());
//饲养员小李喂养十只猴子
for
(
int
i =
0
; i <
10
; i++) {
ans.add(
new
Monkey());
}
//饲养员小李喂养5只鸽子
for
(
int
i =
0
; i <
5
; i++) {
ans.add(
new
Pigeon());
}
f.feedAnimals(ans);
}
}
class
Feeder {
public
String name;
Feeder(String name) {
this
.name = name;
}
//Vector<T>是JDK中提供的一个对象集合,可以随时向其中加入或移除对象
public
void
feedAnimals(Vector<Animal> ans) {
for
(Animal an : ans) {
an.eat();
}
}
}
abstract
class
Animal {
public
abstract
void
eat();
}
class
Lion
extends
Animal {
public
void
eat() {
System.out.println(
"我不吃肉谁敢吃肉!"
);
}
}
class
Monkey
extends
Animal {
public
void
eat() {
System.out.println(
"我什么都吃,尤其喜欢香蕉。"
);
}
}
class
Pigeon
extends
Animal {
public
void
eat() {
System.out.println(
"我要减肥,所以每天只吃一点大米。"
);
}
}
|
总结:
多态编程有两种主要形式:
(1)继承多态:示例程序使用的方法
(2)接口多态:使用接口代替抽象基类。
使用多态最大的好处是:
当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“较”字!程序规模越大,其优势就越突出。
|