java编程思想(八): 多态

多态

多态的主要优点是消除类型之间的耦合关系,可以将类型之间的公共部分放到基类,则所有导出类可以看成是基类,编程只需面向基类,只需一份代码而且增加新的导出类,代码无需更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Shape {
public void draw(){System.out.println("draw Shape");}
}

class Triangle extends Shape{
@Override
public void draw(){System.out.println("draw Triangle");}
}

class Circle extends Shape{
@Override
public void draw(){System.out.println("draw Circle");}
}

public class DrawShape{
public static void drawShape(Shape shape){
shape.draw();
}
public static void main(String[] args){
Circle circle = new Circle();
drawShape(circle);
}
}

如上,对于drawShape(Shape shape)方法,如果没有多态的话,需要写drawShape(Circle circle), drawShape(Triangle triangle) 等方法,且每增加一个导出类,都需要增加一个对应方法。
但是通过多态,可以将这些公共的部分提取出来,直接面向基类Shape编程。而且对于新的导出类,drawShape的代码不需要改变。

动态绑定

1
2
3
public static void drawShape(Shape shape){
shape.draw();
}

上面的代码中,编译器是不知道shape引用是指向Circle对象还是Triangle对象的。所以编译器不知道要调用哪个方法。

解决这个问题的方法是动态绑定。因为要知道调用哪个方法,必须要区分Circle,Triangle等对象,所以需要在对象中存储某种类型信息。动态绑定的机制就是在运行时根据对象的类型绑定相应的方法。

java中除了static、final的方法之外,其他方法默认都是后期绑定的,所以要关闭动态绑定,只需将某个声明为final。

构造器

因为导出类由基类派生,所以要构造导出类,首先需要构造基类。
导出类的构造器调用之前,需要先调用基类的构造器,如果导出类没有通过super()显式调用,则会调用基类默认构造器,如果不存在,则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Meal{
// Meal(){System.out.println("Meal()");}
Meal(String str){ System.out.println("Meal(String str)");}
}

public class Sandwich extends Meal{
public Sandwich(){
// super("haha");
System.out.println("Sanwich()");
}

public static void main(String[] args){
Sandwich sandwich = new Sandwich();
}
}

// 代码运行报错,因为Sandwich中没有显式调用Meal的构造器,而且Meal中由于有了Meal(String str)构造器,编译器无法自动合成默认构造器。

可以加入super("haha")显式调用基类的构造器。

构造器调用顺序

要构造出导出类对象,首先得构造出基类对象。

  1. 将分配给对象的存储空间初始化为二进制0
  2. 先调用基类构造器
  3. 按声明顺序调用成员初始化方法
  4. 调用导出类构造器实体
    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
    class Meal {
    private Bread bread = new Bread();
    Meal() { System.out.println("Meal()"); }
    }

    class Bread {
    Bread() { System.out.println("Bread()"); }
    }

    class Cheese {
    Cheese() { System.out.println("Cheese()"); }
    }

    class Lettuce {
    Lettuce() { System.out.println("Lettuce()"); }
    }

    class Lunch extends Meal {
    Lunch() { System.out.println("Lunch()"); }
    }

    class PortableLunch extends Lunch {
    PortableLunch() { System.out.println("PortableLunch()");}
    }

    public class Sandwich extends PortableLunch {
    private Bread b = new Bread();
    private Cheese c = new Cheese();
    private Lettuce l = new Lettuce();
    public Sandwich() { System.out.println("Sandwich()"); }
    public static void main(String[] args) {
    new Sandwich();
    }
    }

    /*output:
    Bread()
    Meal()
    Lunch()
    PortableLunch()
    Bread()
    Cheese()
    Lettuce()
    Sandwich()
    */

构造器内调用多态方法

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
import static net.mindview.util.Print.*;

class Glyph {
void draw() { print("Glyph.draw()"); }
Glyph() {
print("Glyph() before draw()");
draw();
print("Glyph() after draw()");
}
}

class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() {
print("RoundGlyph.draw(), radius = " + radius);
}
}

public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
} /* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~

上面的代码中先调用了基类构造器Glyph()时,draw()由于动态绑定,会调用RoundGlyph中的draw方法,而此时radius尚未初始化,只是在一开始存储空间初始化为0。

所以在构造器中,最好不要调用非final的方法。

向下转型

向上转型时安全的,因为导出类有基类的所有接口。但是向下转型却未必正确。所以在Java语言中,所有转型都会得到检查,由于类型信息存在运行时的对象中,可以进行RTTI(运行时类型识别),检查向下转型是否正确,错误则抛出ClassCastException。

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 Useful {
public void f() {}
public void g() {}
}

class MoreUseful extends Useful {
public void f() {}
public void g() {}
public void u() {}
public void v() {}
public void w() {}
}

public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
} ///:~

// ouput: java.lang.ClassCastException: class Useful cannot be cast to class MoreUseful