复用代码是java众多引人注目的功能之一。但是想要成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。
一、继承语法
继承是所有OOP语言和JAVA语言不可缺少的组成部分,当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则,就是隐式地从java的标准根类Object继承。
继承并不只是复制基类的接口,当创建了一个导出类的对象时,该对象包含了一个基类的子对象,这个子对象与用基类直接创建对象是一样的,二者的区别在于,后者来自外部,而基类的子对象被包装在导出类对象的内部。对于基类子对象的正确初始化也是至关重要的,而且也仅有一种方法来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。class Art{
Art(){
System.out.println("art constructor");
}
}
class Drawing extends Art{
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 c = new Cartoon();
}
}
运行结果:
art constructor
drawing constructor
cartoon constructor
构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显示地编写调用基类构造器的语句,并传入合适的参数列表。对上面的代码稍作修改class Art{
Art(int i){
System.out.println("art constructor");
}
}
class Drawing extends Art{
Drawing(int i){
super(i);
System.out.println("drawing constructor");
}
}
public class Cartoon extends Drawing{
public Cartoon(){
super(11);
System.out.println("cartoon constructor");
}
public static void main(String[] args){
Cartoon c = new Cartoon();
}
}
运行结果没有变化,但是如果不显示地调用基类的构造方法,编译器将会报错,无法找到基类的构造器。而且,调用基类构造器必须是在导出类构造器中要做的第一件事。
二、向上转型
由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。由于向上转型是从一个较专用类型向较通用类型转换,所以总是很安全的。
三、final关键字
根据上下文环境,java的关键字final的含义存在着细微的差别,但通常它是指“这是无法改变的”。
1.final数据
有些数据的恒定不变时很有用的,比如:一个永不改变的编译时常量,一个在运行时被初始化的值,而你不希望它被改变。带有恒定初始值(即编译期常量)的final static基本类型全用大写字母命名,并且字与字之间用下划线隔开。java允许在参数列表中以声明的方式将参数指定为final,这意味着你无法在方法中更改参数引用所指向的对象。
2.final方法
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义,这是出于设计的考虑,想要确保在继承中使方法行为保持不变,并且不会被覆盖。过去的第二个原因是效率,在java的早期实现中,如果将一个方法指明为final方法,就是同意编译器将针对该方法的所有调用都转为内嵌调用。但是这种做法正在逐渐受到劝阻,应该让编译器和jvm去处理效率问题,只有在想明确禁止覆盖时,才将方法设置为final。
3.final类
当将某个类的整体定义为final时,就说明你不打算继承该类,而且也不允许别人这样做。
四、初始化及类的加载
在对类的加载过程中,编译器会注意到它是否有基类,如果有,则会加载基类,不管你是否打算产生一个该基类的对象,这都要发生。如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的static会被初始化,然后是下一个导出类,以此类推,这种方式很重要,因为导出类的static初始化可能会依赖于基类成员是否被正确初始化。