复用类

复用一般有两种形式,分别是组合和继承。

组合

只需在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成,所以这种方法称为组合。该方法只是复用了现有程序代码的功能,而非它的形式。

继承

按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新代码。

当创建一个类时,总是在继承,除非已明确指出要从其他类中继承,否则就是在隐式地从 Java 的标准根类 Object 进行继承。利用 extends 关键字来声明从其他类中继承, super 关键字表示超类(superclass),即当前类所继承的类,表达式 super.f() 将调用基类中的 f() 方法。

可以为每个类都创建一个 main() 方法,这种技术可使每个类的单元测试都变得简单易行。而且在完成单元测试之后,也无需删除 main(),可以将其留待下次测试。

考虑到其他包中的类可能需要继承,所以,一般的规则是将所有的数据成员都指定为 private,将所有的方法都指定为 public

构建过程是从基类向外扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。当基类中没有默认构造器时,必须在第一时间用关键字 super显式地编写调用基类构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {

A(int i) {
System.out.println("A: " + i);
}
}

class B extends A {

B() {
super(0);
System.out.println("B");
}
}

B b = new B();

// A: 0
// B

代理

这是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构建的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A {

void f() {
System.out.println("A: f");
}
}

class B {

private A a = new A();

public void f() {
a.f();
}

}

清理

由于垃圾回收的不确定性,在需要清理时,最好编写自己的清理方法,但不要使用 finalize()。在编写清理方法时,必须要注意对基类清理方法和成员对象清理方法的调用顺序,以防某个子对象依赖于另一个子对象的情形发生。一般而言,执行类的所有特定的清理动作,其顺序同生成顺序相反。

名称屏蔽

如果 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
class A {

void f(int i) {
System.out.println("A: int");
}

void f(char i) {
System.out.println("A: char");
}
}

class B extends A {

void f(double i) {
System.out.println("B: double");
}

@Override
void f(int i) {
System.out.println("B: int");
}
}

B b = new B();
b.f(1); // B: int
b.f(1.0); // B: double
b.f('f'); // A: char

使用 @Override 注解,在偶然用重载代替覆盖时,编译器会产生错误,例如:

1
2
3
4
5
@Override
// Method does not override method from its superclass
void f(double i) {
System.out.println("B: double");
}

final 关键字

通常出于两种理由使用 final:设计或效率。

final 数据

一个既是 static 又是 final 的域只占据一段不能改变的存储空间。

对于基本类型来说,final 使数值恒定不变。对于对象引用,final 使引用恒定不变,即无法改为指向另一个对象,但是对象其自身却是可以被修改。

final 标记的数据,既可以在编译时就明确,也可以在运行时初始化:

1
2
static final int a = 1;
static final int b = new Random().nextInt(10);

Java 允许有空白的 final,但是必须在域的定义处或者每个构造器中用表达式对 final 进行赋值:

1
2
3
4
5
6
7
8
9
class A {

final int a; // Blank final

A() {
a = 1;
}

}

Java 允许在参数列表中以声明的方式将参数指明为 final,这意味着无法在方法中更改参数引用所指向的对象。这一特征主要用来向匿名内部类传递数据。

1
2
3
4
5
6
7
8
9
10
11
class A {

void f(final int i) {
i = 10; // Cannot assign a value to final variable 'i'
}

void f(final B b) {
b = new B(); // Cannot assign a value to final variable 'b'
}

}

final 方法

使用 final 方法的主要原因是锁定方法,以防任何继承类修改它的含义。

类中所有的 private 方法都隐式地指定为 final。由于 private 方法是隐藏于类中的,即使导出类中存在与 private 方法相同的名称时,也仅仅作为导出类中的新方法,并不是覆盖。

final 类

当将某个类的整体定义为 final 时,这个类将没有子类。final 类中的方法都隐式指定为 final