一个最经典的程序是这样的:
public class ExtendsInstanceTest {
private String baseName = "base";
public ExtendsInstanceTest() {
callName();
}
public void callName() {
System.out.println(baseName);
}
static class Sub extends ExtendsInstanceTest {
private String baseName = "sub";
public void callName() {
System.out.println(baseName);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ExtendsInstanceTest b = new Sub();
}
}
他的输出结果是 null
1)
上面程序最大的难点,也是最重要的地方就是:在父类的构造函数中调用了虚函数,并且这个函数被子类重载了
2)
继承的时候,子类与父类有着同名的属性和同名的方法,关于同名的属性的初始化过程也是必须要了解的。同名的属性会不会被覆盖掉,同名的方法就是多态,同名的方法之间的调用是怎么样的。
3)
类构造的时候,Java机制是到底先给属性分配空间并赋值,还是先处理 构造函数,换句话说,当我们使用new操作符生成一个对象的实例的时候,类的加载机制是怎么样的,
如果这三个问题都搞定了,都理解了,上面的程序就很容易理解为什么输出是null了
Java机制里面有这样的一个原则就是:如果父类存在,子类可以不存在;如果子类存在,父类必须存在;
怎么理解上面的这句话呢,可以用实际的例子来说明,一个人结婚了但是没有小孩,对应着前半句的意思;如果他生了小孩,那么这个小孩子是一定有父亲的
到Java代码中这样看,如果我们实例化一个子类,必须先构造这个子类的父类,否则是错误的。也就是说,父类的存储空间的分配是在子类前面完成的;还可以这样说,当执行到子类的构造函数的时候,首先第一个代码是执行super(),哪怕你没有显示的写出来,他也是会去执行父类的实例化,这就是子类如果想完成初始化,必须先把父类搞定。
Java类加载的机制是第二个需要理解的地方就是:
1)类加载机制首先是 分配内存空间(堆空间,物理存储地址,每个属性都需要分配物理空间,【方法是不需要的】,且这个时候物理空间指向的是空null);
2)当空间分配好之后,进行属性初始化,把值放在栈空间中,前面的第一步过程中物理空间存储地址 指向 这个栈空间,这样就完成了属性值的初始化;
3)当属性值完成了初始化的时候,就开始调用构造函数了,执行构造函数里面的代码块
这个过程说白了,就是一个类加载的时候,执行过程,必须等所有的存储空间都分配好,才能够赋值,而不是一个属性分配好变量之后立刻就赋值,这个理解是错误的。
Java 中子类加载的机制是第三个需要理解的地方:
1)相关的类的加载机制还是跟 上面第二点相似,只是在子类初始化的时候必须先去初始化父类
2)只有 等Java机制给子类和所有的父类都分配了内存空间之后,先搞定堆内存,指向null;才会去 进行属性值的初始化,也就是在栈空间里面是属性的内容,前面分配的内存空间地址这个时候就指向 栈内存的 值;
3)最后就是注意 同名属性不会被子类给覆盖掉的,只是把父类的隐藏掉;同名方法是多态,只会去调用子类的重载方法,
这个规则说白了,就是当有父类和子类的时候,必须都所有的存储空间都分配好了,才能执行 属性的初始化,继而是构造函数;同时要明白一点,子类的构造函数是在父类的构造完成之后才会去执行,必须遵行只有了父亲才有孩子这个规则
上面的三个东西搞明白了,上面的程序就更好理解了
在main函数里面:new 了一个子类 Sub,Java机制是这样做的:
1)第一步首先在堆内存里面,为Sub分配内存空间,主要是属性 baseName ,地址变量指向null,
2)接下来执行Sub的构造函数,首先是执行super()函数,把父类搞出来,
3)进入父类的实例化,首先需要去在堆内存里面给父类分配内存空间,为父类的baseName分配地址,地址变量指向null;
4)由于父类不需要再也没有超类了,那么这个时候父类和子类的内存分配都做完了,接下来就是需要为 属性进行初始化的工作
5)首先是给父类的baseName执行初始化操作,在栈内存里面写上内容base,上面的为父类分配的地址变量 指向 这个栈内存
6)接下来是做父类的构造函数,完成父类的实例化,构造函数里面的代码是执行了一个虚函数,这个时候首先要看子类有没有重载这个函数,多态的调用
7)子类有重载,所以调用子类的方法,但是子类的baseName还没有初始化,所以就没有直接打出null了
8)父类创建完毕,接下来就是去执行子类的创建工作了,
9)首先为子类的属性进行初始化,在栈内存里面放上内容 sub;
10)接下来是去执行子类的构造函数,没有,是默认的无参
整个过程就完整了,
上面的例子最主要的就还是:关于在构造函数里面执行多态方法的时候,应该注意的地方