五. 复用和final关键字的使用
对于像 C 语言等面向过程语言来说,“复用”通常指的就是“复制代码”。任何语言都可通过简单复制来达到代码复用的目的,但是这样做的效果并不好。Java 围绕“类”(Class)来解决问题。我们可以直接使用别人构建或调试过的代码,而非创建新类、重新开始。以下介绍两种方式:组合和继承。
1. 组合语法
简单来说,就是把对象的引用放置在一个新的类里,便是使用了组合。
编译器不会为每个引用创建一个默认对象,因为在许多情况下,这会导致不必要的开销。
初始化引用有四种方法:
- 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。
- 在该类的构造函数中。
- 在实际使用对象之前。这通常称为延迟初始化。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。
- 使用实例初始化。
2. 继承语法
在创建类时一定有继承,因为除了显式地继承其他类,就肯定隐式地继承 Java 的标准根类对象(Object)。
使用关键字 extends 后跟基类的名称,将自动获得基类中的所有字段和方法。(基类中被private修饰的只能在本类中使用,子类中无法使用)
3. 组合与继承的选择
组合和继承都允许在新类中放置子对象(组合是显式的,而继承是隐式的)。
当你想在新类中包含一个已有类的功能时,使用组合,而非继承。也就是说,在新类中嵌入一个对象(通常是私有的),以实现其功能。
有时让类的用户直接访问到新类中的组合成分是有意义的,只需将成员对象声明为 public 即可。成员对象隐藏了具体实现,所以这是安全的。
继承一般是“is a”的关系表达的,而“has a”的关系可以用组合来表达。
一个比较好的选择方法:在考虑一个类该用组合还是继承时,可以问一问自己是否未来可能要将其向上转型,如果需要就必须使用继承。
4. final关键字
根据上下文环境,Java 的关键字 final 的含义可能有一点的不同,但一般都是指“这是不能被改变的”。
final 可以用在三个地方:数据、方法和类。
final数据
许多编程语言都有某种方法告诉编译器有一块数据是恒定不变的。恒定是有用的,如:
- 一个永不改变的编译时常量。
- 一个在运行时初始化就不会改变的值。
一个被 static 和 final 同时修饰的属性只会占用一段不能改变的存储空间。
而当用 final 修饰对象引用而非基本类型时,final 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。这对于数组具有同样的意义,数组只不过是另一种引用。一般来说,声明引用为 final 没有声明基本类型为 final 有用。
注意:编译器确保空白 final 在使用前必须被初始化,这样既能使一个类的每个对象的 final 属性值不同,也能保持它的不变性。所以必须在定义时或在每个构造器中执行 final 变量的赋值操作。
final参数
在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量。如下例代码:
class Test {
//void func1(final int i) { i++; } // 无法改变i
// 只能读取
int func2(final int i) {
return i + 1;
}
}
只能读取而不能修改参数,这个特性主要用于传递数据给匿名内部类。
final方法
一般使用 final 方法是为了防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。
final和private
类中所有的 private 方法都被隐式地指定为 final。因为不能访问 private 方法,所以不能覆写它。可以给 private 方法添加 final 修饰,但是并不能给方法带来额外的含义。
当你试图覆写一个 private 方法(隐式是 final 的)时,看上去奏效,而且编译器不会给出错误信息。代码如下:
public class S {
public static void main(String[] args) {
Test2 t2 = new Test2();
t2.func();
// ((Test)t2).func(); 此处无法调用
}
}
class Test {
private void func(){
System.out.println("This is Test.");
}
}
class Test2 extends Test {
public void func(){
System.out.println("This is Test2.");
}
}
如果一个方法是 private 的,它就不是基类“接口(可以被其他类使用)”的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。如果你在派生类中以相同的命名创建了 public,protected 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。
final类
一个类为 final 类时,就意味着它不能被继承。当一个类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类,可将其定义为 final 类。
由于 final 类禁止继承,类中所有的方法都被隐式地指定为 final,所以没有办法覆写它们。可以在 final 类中的方法加上 final 修饰符,但不会增加任何意义。
final的使用忠告
在设计类时将一个方法指明为 final 看上去是明智的,可能会觉得没人会覆写那个方法,有时这是对的。
但是通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 final,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。
参考资料:On Java 8