您的位置:首页 > 编程语言 > Java开发

【Java】多态和动态绑定中的坑

2016-05-07 19:27 537 查看
大一学C++的时候就感觉多态这部分略烦,小坑不断。一年过后学习Java再次遇到多态的问题,写一篇笔记来记下发现的各种小坑,以供以后复习。

来看下面一段代码:

enum Note {
MIDDLE_C,
C_SHARP,
B_FLAT
}

class Instrument {
public int field = 0;

void play(Note note) {
System.out.println("Instrument.play() " + note);
}

String what() {
return "Instrument";
}

void adjust() {
System.out.println("Adjust Instrument");
}
}

class Wind extends Instrument {
public int field = 1;

@Override
void play(Note note) {
System.out.println("Wind.play() " + note);
}

@Override
String what() {
return "Wind";
}

@Override
void adjust() {
System.out.println("Adjust Wind");
}
}

class Brass extends Wind {
@Override
void play(Note note) {
System.out.println("Brass.play() " + note);
}

@Override
void adjust() {
System.out.println("Adjust Brass");
}
}

public class Music3 {

public static void tune(Instrument instrument) {
instrument.play(Note.C_SHARP);
}

public static void tuneAll(Instrument[] instruments) {
for (Instrument instrument : instruments) {
tune(instrument);
}
}

public static void main(String... args) {
Instrument[] orchestra = {
new Wind(),
new Brass()
};
tuneAll(orchestra);
}

}

/*
Output:
Wind.play() C_SHARP
0
Brass.play() C_SHARP
0
*/


当创建orchestra数组时,如果new的是导出类,那么他会自动向上转型成Instrument。因为Wind和Brass毕竟是继承自Instrument的,所以Instrument的接口必定存在于它导出的类中。

在Main函数中,tune方法接收一个Instrument引用,但同时也接受任何导出自Instrument的类。那么在这种情况下编译器怎样才能知道这个Instrument引用指向的是Wind对象,而不是Brass呢?实际上,编译器也不知道。。那么这个问题怎么解决呢?答案是采用动态绑定技术。

动态绑定是指在运行时根据对象的类型,将一个方法调用同一个方法主体关联起来。也就是说编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。因此,tune方法就可以正确调用导出类重写的相应方法。

Note: Java中除了static方法和final方法(private方法也属于final方法)之外,其他所有方法都是动态绑定的。当然,这样做有很多好处,也有一定的麻烦。

我们来看看麻烦在哪里:

class Glyph {
void draw() {
System.out.println("Glyph.draw()");
}

private void duplicate() {
System.out.println("Glyph.duplicate()");
}

Glyph() {
System.out.println("Glyph() before draw");
draw();// 动态绑定,调用子类覆盖的draw
duplicate();// 非动态绑定,调用Glyph的duplicate
4000

System.out.println("Glyph() after draw");
}
}

class RoundGlyph extends Glyph {
private int radius = 1;

RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.RoundGlyph().radius = " + radius);
}

@Override
void draw() {
System.out.println("RoundGlyph.draw().radius = " + radius);
}

public void duplicate() {
// 这个duplicate和父类的duplicate没有任何关系
// 不是Override
// 因为父类的duplicate是private final的
System.out.println("RoundGlyph.duplicate()");
}
}

public class PolyConstructors {
public static void main(String...args) {
new RoundGlyph(10);
}
}

/*
Output:
Glyph() before draw
RoundGlyph.draw().radius = 0
Glyph.duplicate()
Glyph() after draw
RoundGlyph.RoundGlyph().radius = 10
*/


首先要说明一下Java初始化对象的实际过程:

在做任何事情之前,先将分配给对象的存储空间初始化成二进制的零

自下向上调用基类构造器。这里在Glyph的构造器中会调用覆盖了的draw()方法,而不是Glyph的draw()。由于步骤1的缘故,我们会发现radius的值是0

完所有基类的初始化后,按照声明的顺序调用当前成员的初始化方法。这里会将radius初始化为1

调用导出类的构造器主体

第一次看到这样的结果时我的内心其实是拒绝的。。觉得Glyph构造器里调用的draw()方法理所当然的就应当是Glyph.draw()。然而这是一个坑,很容易被忽视掉,而且不好debug。

Note: 在构造器内唯一能够安全调用的方法是基类中的final方法,当然包括private方法。这些方法不会被导出类覆盖,因此也就不会出现掉进上面这个坑的情况了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多态