第七章 复用类
复用代码是 java 众多引人注目的功能之一。但是想要成为极具革命性的语言,仅仅能够复制代码并对其加以改变是不够的,他还必须能够做更多的事情。
1. 复用的两种形式
- 一般来说组合和继承都是组合使用的。
- 组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承是隐式的这样做。
- 组合技术通常用于想在新类中时使用现有类的功能而非它的接口。即,在新类中嵌入某个对象,让其实现所需要的功能,但是新类的用户看到的只是为新类所定义的接口,而非所嵌入的对象的接口,为了取得这种效果,需要在新类中嵌入一个现有类的 private 对象
- 在实际项目中,经常会想要将某些事物尽可能的对这个世界隐藏起来,但仍然允许导出类的成员访问他们。关键字 protected 就是起这个作用的,他指明 “ 就类用户而言,这是 private 的,但是对于任何继承于此类的导出类或者其他任何位于同一个包内的类来说,他都是可以访问的 ” , protected 也提供了包内访问权限 。
- 由导出类转换为基类,在继承图中是向上移动的,因此一般称为向上转型,由于向上转型是一个较专用类型向较通用类型转换,所以是很安全的。
- 导出类是基类的一个超集,他可能比基类含有更多的方法,但它必须至少具备基类中所含有的方法 。
- 继承并不是一定要使用的,在使用的时候一定要考虑清楚是否要使用,如果必须向上转型,那么继承是必须的,如果不需要向上转型,那么在使用继承前需要考虑清楚。
2. 组合
- 只需要在新的类中产生现有的类的对象。由于新的类是由现有的对象组成的,所以这种方式称为组合,组合只是复用了现有程序代码的功能,而非它的形式。
- 组合,只需要将对象引用置于新的类中即可。
- 每一个非基本类型的对象都有一个 toString() 方法,基本类型会自动被初始化为 0 ,对象引用会被初始化为 null ,编译器不会默认的为每一个引用都创建一个默认对象。如果想初始化这些引用,有四种方法:
- 在定义对象的时候。这意味着他们总是能够在构造器被调用之前被初始化;
- 在类的构造器中;
- 在正要使用这些对象的时候,这种方式被称为惰性初始化;
- 使用实例初始化。
3.继承
- 继承按照现有的类的类型来创建新类,无需改变现有的类的形式,采用现有的类形式并在其中添加新的代码,这种神奇的方式称为继承。继承是面向对象程序设计的基石。
- 当创建一个类的的时候,总是在继承,因为,除非已经明确指出要从其他类中继承,否则就是在隐式地从 java 的标准根类 Object 中进行继承。使用 extends 继承后,会自动得到基类中的所有域和方法。
- 为了继承,一般将所有的数据成员都指定为 private ,将所有的方法指定为 public
- 处理 string 对象的操作符 “ += ” 、 “ + ”
- super 关键字 表示超类 , 当前类是从超类中继承过来的,super 关键字只能在继承中使用, super.xxx() 表示调用基类的 xxx() 方法 , 而不是当前类的 xxx() 方法。
- 继承完成之后,除了可以调用当前类的方法,还可以调用继承的基类的所有可用方法。
- 当创建了一个导出类的对象的时候,这个对象包含了一个基类的子对象,这个子对象与你用基类直接创建出来的对象是一样的。二者的区别在于,后者来自外部,而基类的子对象被包装在导出类的对象的内部。
- 初始化基类子对象是很重要的,只能在基类构造器中进行初始化,java 会自动在导出类的构造器中插入对基类构造器的调用,初始化(构造过程)是从基类向外进行扩散的,即先初始化基类,然后初始化第一次继承基类的类,然后初始化继承继承了基类的继承类,最后初始化被调用的类。
4. 代理
- 使用代理可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集。
5. 确保正确的清理
- 析构函数是一种在对象被销毁时,可以自动被调用的函数,但是 java 中并不存在析构函数。因为垃圾回收并不知道什么时候会被调用,所以需要显示的编写一个方法来完成这件事情。必须将这一清理动作置于 finally 字句中,防止异常的出现。
- 关键字 try 表示 ,下面的用大括号扩起来的是所谓的保护区( guarded region) ,这意味着他需要被特殊处理,其中一项特殊处理就是无论 try 块 是怎么样推出的,保护区后面的 finally 子句中的总是要被执行的。
6.名称屏蔽
- 如果 java 的基类拥有某个已经被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类的中的任何版本。
- 对于注解 “ Override ” ,想要覆写某个方法的时候,可以选择添加这个注解。 override 可以防止你在不想重载的时候而意外的进行了重载。
7. final关键字
- final 数据 ,来向编译器告知一块数据是恒定不变的,有时候数据的恒定不变是很有用的。
- 一个永远不会改变的编译时常量
- 一个在运行时被初始化的值,但是你不希望它改变。
- 一个既是 final 又是 static 的 域 , 只占用一段不能改变的存储空间。
- 对于对象而言, final 使其引用恒定不变,一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象,然而,对象自身却是可以被修改的。
- 带有恒定初始值( 编译期常量 )的 final static 基本类型全用大写字母命名,并且字与字之间用下划线隔开,
1. 空白 final
java 允许生成空白 final ,所谓空白 final 是指声明为 final 但是为给定初值的域,但是无论什么情况,都需要确保 final 在使用前必须被初始化。必须在域的定义出或者每个构造器中用正则表达式对 final 进行赋值,这正是 final 域在使用前总是被初始化的原因所在。
2. final 参数
java 允许在参数列表中以声明的方式将参数指明为 final ,这意味着你无法在方法中更改参数引用所指向的对象。
3. final 方法
使用 final 方法的原因之一是把方法锁住,以防止任何继承类修改它的含义,第二个原因是效率。
类中的所有 private 方法都隐式的指定为是 final 的 ,由于无法取用 private 方法,所以也就无法覆盖它。
如果尝试覆盖一个 private 方法,可能是奏效的,编译器不会报错,但是并没有什么用。 “覆盖” 只有在某方法是基类的接口的一部分的时候才会出现,
4. final 类
当将某个类的整体定义为 final 的时候,就表明不希望他有子类。final 类的域可以个
人选择是不是 final , 由于 final 类禁止继承,所以域中的方法隐式的是 final 的。
8. 初始化及类的加载
在java中的加载发生于创建类的第一个对象的时候,但是当访问 static 域或者 static 方法时,也会发生加载。下面是一个示例 :
package FiveTest;
class Insert{
private int i = 9 ;
protected int j ;
Insert(){
System.out.println(" i = " + i + ". j = " + j);
j = 39 ;
}
private static int x1 = printIniy("static Insect.x1 initialized ") ;
static int printIniy(String s){
System.out.println(s);
return 47 ;
}
}
public class FiveTest7 extends Insert{
private int k = printIniy("FiveTest.k initialized") ;
public FiveTest7(){
System.out.println(" k = " + k);
System.out.println(" j = " + j);
}
private static int x2 = printIniy("static FiveTest.x2 initialized") ;
public static void main(String[] args) {
System.out.println("FiveTest constructor ===== ");
FiveTest7 b = new FiveTest7() ;
}
}
//执行结果
static Insect.x1 initialized
static FiveTest.x2 initialized
FiveTest constructor =====
i = 9. j = 0
FiveTest.k initialized
k = 47
j = 39
关于这段代码的执行过程
- 运行时先试图访问 main() 方法(一个 static 方法) ,加载器开始启动并找出 FiveTest7 类的编译代码。 在对这个类进行加载的过程中,编译器注意到他有一个基类 (由关键字 extends 得出) ,于是继续进行加载 , 不管是否打算产生一个该类的对象,这件事情都要发生。
- 如果这个类还有他自身的基类,那么第二个基类机会被加载,以此类推。
- 接下来 , 根基类中的 static 初始化 ( Insert ) 会被执行,然后是下一个导出类,以此类推,这种方式很重要,因为导出类的 static 初始化可能会依赖于基类成员能否被正确的初始化 。
- 之后必要的类加载完毕,开始创建对象。首先,对象中的所有基本类型都会被设置为 null ,这是通过将对象内存设为二进制零而一举生成的。
- 之后,基类的构造器就会被调用,之后导出类的构造器被调用,构造器初始化完成后,实例变量按照顺序被初始化。
由本人从 Thinking in java ( java 编程思想 ) 整理而来