组合语法
只需将对象引用置于新类中即可。例如,假设你需要某个对象,它要具有多个 String 对象、几个基本类型数据,以及另一个类的对象。对于非基本类型的对象,必须将其引用置于新的类中,但可以直接定义基本类型数据。
如果想初始化这些引用,可以在代码中的下列位置进行:
- 在定义对象的地方。意味着他们总是能够在构造器被调用之前被初始化。
- 在类的构造器中
- 在正要使用这些对象之前,这种方式被称为惰性初始化。
- 使用实例初始化。
继承语法
当用关键字 extends 继承了类后,新类会自动得到基类中所有的域和方法。
使用基类中定义的方法及修改是可行的。如果 想在重写的方法中调用基类中相同的方法,可以使用super.method(),Java 使用 super 关键字表示超类的意思。
初始化基类
从外部类来看,导出类就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。
所有,对基类子对象的正确初始化是至关重要的。而且仅有一种方法来保证这一点:在构造器中调用基类构造器来初始化。Java 会自动在导出类的构造器中插入基类构造器的调用。
带参数的构造器
前面所说的自动调用只适用于都有默认构造器的情况,对于没有默认构造器,或者想调用带参基类构造器,就必须用 super 显示地编写调用基类构造器的语句,并且配以适当的参数列表。
代理
继承与组合之间的中庸之道,因为==我们将一个成员对象置于所要构造的类中(就像组合),但与此同时,在心累中暴露了该成员对象的所有方法(就像继承)。
下面简要说明,当想要构造一艘太空船到时候,可能会用到一个控制器,如果继承了控制器类,那么当造好了飞船,就可以告诉飞船向前运动,但此时的飞船并不是真正的飞船控制器。
所以,应该是飞船包含控制器,并且控制器的所有方法在飞船中都暴露出来。代理可以解决这个问题。
public class SpaceShipDelegation {
private SpaceShipControls controls =
new SpaceShipControls();
public void back(int i) {
controls.back(i);
}
}
结合使用组合和继承
总结:组合、继承和代理需要综合考量之后,做合适的选取。组合和代理很相似,仔细审视,能发现区别。
确保正确清理
一言以蔽之,首先,执行类的所有特定的清理动作,顺序同生成顺序相反(通常这就要求基类元素仍旧存活);然后,调用基类的清理方法。
为的是防止某个子对象依赖于另一个子对象情形的发生。
名称屏蔽
如果 Java 的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本。因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作(注意这个重写没有关系):
class Homer {
char doh(char c) {
}
float doh(folat f) {
}
}
class Brat extends Homer {
void doh(int i) {
}
public static void main(String[] args ) {
Bart b = new Bart();
b.doh(1);
b.doh('x');
b.doh(1.0f)
}
}
而在既重载又重写了同一个方法的时候,可以使用 @Override 来加以区分。
在组合与继承之间选择
组合和继承都允许在新的类中放置子对象,组合是显示地这样做,而继承则是隐式地做。
区别:
- 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。一般在新类中嵌入一个现有类的 private 对象。
- 有时,允许类的用户直接访问新类中的组合成分是极具意义的;也就是说,将成员对象声明为 public。
向上转型
继承技术中最重要的方面,是用来表现新类和基类之间的关系,这种关系可以用新类是现有类的一种类型这句话来加以概括。
由于继承可以确保基类中所有的方法在导出类中也同样有效,所以能够向基类发送的所有消息同样也可以向导出类发送。
为什么称为向上转型
由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。由于向上转型是从一个较专用类型向较通用类型转换,所以总很安全的。导出类是基类的一个超集。在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法。
再论组合与继承
总结:尽可能少的使用继承,使用前考虑是否需要从新类向基类进行向上转型。如果不需要,就仔细考虑是否需要继承。
final 关键字
根据上下文环境, Java 的关键字 final 的含义存在着细微的区别,但通常指的是“这是无法改变的。”
不想改变的理由有二:
- 设计
- 效率
final 数据
有时数据的恒定不变是很有用的
- 一个永不改变的编译时常量。
- 一个在运行时被初始化的值,而你不希望它被改变。
编译期常量必须是基本数据类型,并且以关键字 final 表示。在对这个常量进行定义的后,必须对其进行赋值。
一个既是 static 又是 final 的域只占据一段不能改变的存储空间。
当对象引用运用 final 时,表示引用恒定不变,无法把它改为指向另一个对象。然而,对象自身却是可以被修改的。这一限制,同样适用数组,它也是对象。
不能因为某数据是 final 的就认为在编译时可以知道它的值,在运行时生成的数值也可以用来初始化。
将 final 定义为静态和非静态的区别,静态的只有一个,非静态的会随着创建第二个类的对象,而产生第二个。这是因为 static ,在装载时已被初始化,而不是每次创建新对象时初始化。
空白 final
所谓空白 final 是指被声明为 final 但又未给定初值的域。
必须在
- 域的定义处
- 每个构造器
用表达式对 final 进行赋值,这正是 final 域在使用前总是被初始化的原因所在。
final 参数
在参数列表中以声明的方式将参数指明为 final 。这意味着无法在方法中更改参数引用所指向的对象。主要用来向匿名内部类传递数据。
final 方法
使用 final 方法原因有两个
- 把方法锁定,以防任何继承类修改它的含义。
- 效率,现在不再需要使用 final 方法来进行优化。
final 和 private 关键字
类中所有的 private 方法都隐式地指定为是 final 的。由于无法取用 private ,所以也就无法覆盖。
但如果在导出类中以相同的名称生成一个 public 、 protected或包访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时,并没有覆盖该方法,仅是生成了一个新的方法。
final 类
final 放置于 class 之前,表明不打算继承该类,而且不允许别人这样做。该类用不需要做任何变动,也不希望它有子类。
final 类的域可以根据个人意愿选择是或不是 final 。然而,由于 final 类禁止继承,所以 final 类中所有的方法都隐式地指定为是 final 的,因为无法覆盖他们,可以给 final 类中的方法添加 final 修饰词,但这不会增添任何意义。
初始化及类的加载
Java 中所有食物都是对象,每个类的编译代码都存在于它自己的独立的文件中。该文件只在需要使用程序代码时才会被加载。一般来说,可以说:“类的代码在初次使用时才加载。”这通常是指加载发生于创建类的第一个对象之时,但是当访问 static 域或 static 方法时,也会发生加载。
所有的 static 对象和 static 代码都会在加载时依程序中的顺序而依次初始化。
继承与初始化
大致概括为:先父类,后子类。
总结
继承和组合都能从现有类型生成新类型。
优先使用组合,只在确实必要时才使用继承。