序
组合:新的类是由现有类的对象所组合。这方法只是复用了现有程序代码的功能。
继承:无需改变现有类的形式,采用现有类的形式并在其中添加新代码。继承是面向对象程序设计的基石之一。
7.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
35
36
37
38
39
/*
* 类中域为基本类型时能够自动被初始化为0。但是对象引用会被初始化为null。
*/
package ThinkingInJava_07_1;
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = "Constructed";
}
public String toString() {
return s;
}
}
public class SprinklerSystem {
private String value1, value2, value3, value4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return "value1 =" + value1 + " " + "value2 =" + value2 + " " + "value3 =" + value3 + " " + "value4 ="
+ value4 + "n" + "i = " + i + " " + "f = " + f + " " + "source = " + source;
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
}
/* Output
* WaterSource()
* value1 =null value2 =null value3 =null value4 =null
* i = 0 f = 0.0 source = Constructed
*/
编译器并不是简单地为每一个引用都创建默认对象,因为若真要那样做的话,就会在许多情况下增加不必要负担。
如果想初始化这些引用,可以在代码中的下列位置进行:
在定义对象的地方。这意味着它们总是能够在构造器被调用之前被初始化。
在类的构造器中
就在正要使用这些对象之前,这种方式称为惰性初始化。在生成对象不值得及不必要每次都生成对象的情况下,这种方式可以减少额外的负担。
使用实例初始化
以下是四种方式的示例:
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
package ThinkingInJava_07_1;
class Soap {
private String s;
Soap() {
System.out.println("Soap()");
s = "Constructed";
}
public String toString() {
return s;
}
}
public class Bath {
// Initializing at point of definition
private String s1 = "Happy", s2 = "Happy", s3, s4;
private Soap castille;
private int i;
private float toy;
public Bath() {
System.out.println("Inside Bath()");
s3 = "Joy";
toy = 3.14f;
castille = new Soap();
}
// Instance initialization
{
i = 47;
}
public String toString() {
if (s4 == null) { // Delayed initialization
s4 = "Joy";
}
return "s1 + " + s1 + "n" + "s2 + " + s2 + "n" + "s3 + " + s3 + "n" + "s4 + " + s4 + "n" + "i = "
+ i + "n" + "toy = " + toy + "n" + "castille = " + castille;
}
public static void main(String[] args) {
Bath b = new Bath();
System.out.println(b);
}
}
/* Output
* Inside Bath()
* Soap()
* s1 + Happy
* s2 + Happy
* s3 + Joy
* s4 + Joy
* i = 47
* toy = 3.14
* castille = Constructed
*/
7.2 继承语法
当创建一个类时,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。
extends关键字实现,会自动得到基类中所有的域和方法。
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
61
62
63
64
65
66
67
68
69
70
/*
* 可以为每个类都创建了一个main()方法。这种方法可使每个类的单元测试都变得简便易行。
* 而且在完成单元测试之后,无需删除,可以留待下次测试。只有命令行所调用那个main()会被调用。
* 继承的一般规则,是将所有的数据成员都指定为private,将所有的方法指定为public
* 继承可视作对类的复用
* 继承可以添加自己的新方法。
*/
package ThinkingInJava_07_2;
class Cleanser {
private String s = "Cleanser";
public void append(String a) {
s += a;
}
public void dilute() {
append(" dilute()");
}
public void apply() {
append(" apply()");
}
public void scrub() {
append(" scrub()");
}
public String toString() {
return s;
}
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute();
x.apply();
x.scrub();
System.out.println(x);
}
}
public class Detergent extends Cleanser {
// Change a method
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() {
append(" foam()");
}
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
System.out.println(x);
System.out.println("Testing base class");
Cleanser.main(args);
}
}
/* Output
* Cleanser dilute() apply() Detergent.scrub() scrub() foam()
* Testing base class
* Cleanser dilute() apply() scrub()
*/
7.2.1 初始化基类
继承并不只是复制基类的接口。
当创建一个导出类的对象时,该对象包含了一个基类的子对象,这个子对象与你用基类直接创建的对象是一样的。
基类的子对象被包装在导出类对象内部。
对基类子对象的正确初始化是至关重要的。
仅有方法保证:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。
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
32
33
/*
* 构建过程是从基类向外扩散。
* 基类在导出类构造器可以访问它之前就已经完成了初始化。
* 即使你不为Cartoon创建构造器,编译器也会为你合成一个默认的构造器,该构造器将调用基类的构造器。
*/
package ThinkingInJava_07_2;
class Art {
public Art() {
System.out.println("Art constructor");
}
}
class Drawing extends Art {
public Drawing() {
System.out.println("Drawing constructor");
}
}
public class Cartoon extends Drawing {
public Cartoon() {
System.out.println("Cartoon constructor");
}
public static void main(String[] args) {
Cartoon x = new Cartoon();
}
}
/* Output
* Art constructor
* Drawing constructor
* * Cartoon constructor
*/
带参数的构造器
如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列 表:
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
/*
* 如果不在BoardGame()中调用基类构造器,编译器将无法找到符合Game()形式的构造器
* 而且,调用基类构造器必须是你在导出类构造器中要做的第一件事。
*/
package ThinkingInJava_07_2;
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame{
Chess() {
super(11);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
}
/*
* Game constructor
* BoardGame constructor
* Chess constructor
*/