Fork me on GitHub

Java中的多态性理解(注意与C++区分)

Java中除了static方法和final方法(private方法本质上属于final方法,因为不能被子类访问)之外,其它所有的方法都是动态绑定,这意味着通常情况下,我们不必判定是否应该进行动态绑定—它会自动发生。

  • final方法会使编译器生成更有效的代码,这也是为什么说声明为final方法能在一定程度上提高性能(效果不明显)。
  • 如果某个方法是静态的,它的行为就不具有多态性:

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
    class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}

public String dynamicGet() {
return "Base dynamicGet()";
}
}

class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}

public String dynamicGet() {
return "Derived dynamicGet()";
}
}

public class StaticPolymorphism {

public static void main(String[] args) {
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}

}

输出 Base staticGet() Derived dynamicGet()


  • 构造函数并不具有多态性,它们实际上是static方法,只不过该static声明是隐式的。因此,构造函数不能够被override。
  • 在父类构造函数内部调用具有多态行为的函数将导致无法预测的结果,因为此时子类对象还没初始化,此时调用子类方法不会得到我们想要的结果。
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
class Glyph {
void draw() {
System.out.println("Glyph.draw()");
}
Glyph() {
System.out.println("Glyph() before draw()");
draw();
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);
}

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

public class PolyConstructors {

public static void main(String[] args) {
new RoundGlyph(5);

}

}

输出 Glyph() before draw() RoundGlyph.draw(). radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(). radius = 5


为什么会这样输出?这就要明确掌握Java中构造函数的调用顺序:

 1. 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0;
 2. 调用基类构造函数。从根开始递归下去,因为多态性此时调用子类覆盖后的draw()方法(要在调用RoundGlyph构造函数之前调用),由于步骤1的缘故,我们此时会发现radius的值为0;
3. 按声明顺序调用成员的初始化方法;
4. 最后调用子类的构造函数。

只有非private方法才可以被覆盖,但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行,即覆盖private方法对子类来说是一个新的方法而非重载方法。因此,在子类中,新方法名最好不要与基类的private方法采取同一名字(虽然没关系,但容易误解,以为能够覆盖基类的private方法)。

Java类中属性域的访问操作都由编译器解析,因此不是多态的。父类和子类的同名属性都会分配不同的存储空间,如下:

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
// Direct field access is determined at compile time.
class Super {
public int field = 0;
public int getField() {
return field;
}
}

class Sub extends Super {
public int field = 1;
public int getField() {
return field;
}
public int getSuperField() {
return super.field;
}
}

public class FieldAccess {

public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.filed = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.filed = " + sub.field +
", sub.getField() = " + sub.getField() +
", sub.getSuperField() = " + sub.getSuperField());
}

}

输出:sup.filed = 0, sup.getField() = 1 sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0
Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并非Super版本的field域,因此为了得到Super.field,必须显式地指明super.field。

本文欢迎转载,但是希望注明出处并给出原文链接。 如果你有任何疑问,欢迎在下方评论区留言,我会尽快答复。 如果你喜欢或者不喜欢这篇文章,欢迎你发邮件到 alonecong@126.com 告诉我你的想法,你的建议对我非常重要。

------ 本文结束感谢您的阅读! ------
0%