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)接口多态:使用接口代替抽象基类。
使用多态最大的好处是:
当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“较”字!程序规模越大,其优势就越突出。
|