9-1,子父类的构造函数-子类的实例化过程
1,在子类构造对象的时候,发现访问子类构造函数时,父类的构造函数也运行了,这时为什么呢?
原因是:在自类的构造函数中第一行有一个默认的隐式语句,就是super()。
super()调用的就是父类中空参的构造函数。
示例:
class Fu{
Fu() {
super();//Fu继承自Object,也有一个super(),访问的是Object的空参构造函数
System.out.println("Fu run ...");
}
}
class Zi extends Fu {
Zi() {
super();//这里访问的是Fu的空参构造函数
System.out.println("Zi run ...");
}
}
class ExtendsDemo {
public static void main(String[] args) {
new Zi();
}
}
运行结果是:
Fu run ...
Zi run ...
在本例中,如果Fu没有空参构造函数,如只有一个Fu(int x),则编译出错,在子类中必须显示的调用父类构造函数,并且也必须在Zi构造函数的第一行,比如super(1),即可解决。
继承时,构造函数不能被继承过来,也不能被覆盖。
2,子类的实例化过程
子类中所有的构造函数默认都会访问父类空参的构造函数。
示例:
class Fu {
Fu() {
super();
System.out.println("A Fu run ...");
}
Fu(int x) {
super();
System.out.println("B Fu run ..." + x);
}
}
class Zi extends Fu {
Zi() {
super();
System.out.println("C Zi run ...");
}
Zi(int x) {
super();
System.out.println("D Zi run ..." + x);
}
}
class ExtendsDemo {
public static void main(String[] args) {
new Zi(6);
}
}
程序结果:
A Fu run ...
D Zi run ...6
实例化过程为:
创建Zi对象,调用Zi的构造函数Zi(int x),在Zi(int x)中有默认的额super(),再调用Fu中的Fu(),Fu()中有super(),调用Object的Object(),这个函数啥都没做,直接return,返回到Fu()中,执行下一句打印,输出A Fu run ...,再返回Zi(int x)输出D Zi run ...6。
若在Zi(int x)中加入super(6),则先调用Fu中的Fu(int x),再输出Zi(intx)中的语句。
9-2,子父类中的构造函数-子类实例化过程细节
1,为什么子类实例化的时候要访问父类中的构造函数呢?
因为子类继承了父类,就取到了父类中的内容(属性),所以在使用父类的内容之前,要先看父类是如何对自己的内容进行初始化的。所以子类在构造对象时,必须访问父类中的构造函数,为了完成这个必须的动作,就在子类的构造函数的第一句中加入了super()语句,这是个默认的隐式语句,不写也会调用。
如果父类中没有定义空参的构造函数,那么子类的构造函数必须用super明确要调用父类中的那个构造函数。
注意:super语句必须定义在子类构造函数的第一行,因为父类的初始化动作要先完成。
子类构造函数中,如果使用this调用了本类的构造函数,那么super这个隐式语句就没有了,因为super和this都只能定义在第一行,所以二者只能存在一个。但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。
2,示例:
class Demo extends Object {//java中所有的类都继承自Object
/* 这是类中自带的默认的构造函数,自带有super
Demo() {
super();
return;
}
*/
}
从这个例子中可以看出,我们写一个Demo类,在这个类什么都没写的时候,就已经具备注释中的内容了。
9-3,子父类构造函数-子类实例化过程-内存图解
1,一个对象的实例化过程:
以Person p = new Person();为例。
(1)JVM会读取指定路径(classpath)下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接父类的情况下)。
(2)在堆内存中开辟空间,分配地址。
(3)并在对象空间中,对对象的属性进行默认的初始化。
(4)调用对应的函数进行初始化。
(5)在构造函数中,第一行会先调用父类中构造函数进行初始化。
(6)父类初始化完毕后,再对子类的属性进行显示初始化。
(7)再进行子类构造函数的特定初始化。
(8)初始化完毕后,将地址值赋给引用变量。
2,示例:
class Fu {
Fu() {
super();
show();
return;
}
void show() {
System.out.println("fu show");
}
}
class Zi extends Fu {
int num = 8;
Zi() {
super();
return;
}
void show() { //覆盖了Fu中的show
System.out.println("zi show ..." + num);
}
}
class ExtendsDemo{
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
运行结果:
zi show ... 0
zi show ... 8
步骤:
(1)ExtendsDemo类的方法加载进方法区。
(2)main方法加载进静态方法区。
(3)main进栈。
(4)Zi z = new Zi();开始加载Zi类,由于Zi类继承了Fu类,所以Fu类先加载进方法区,然后Zi类加载。
(5)z对象进栈。
(6)实例化z对象,再堆中开辟空间,分配地址,num默认初始化为0。
(7)Zi()构造方法进栈,自带this引用,并指向0X0045。
(8)Zi()中super()引用Fu的构造函数Fu(),Fu()构造方法进栈,自带this引用,指向0X0045,执行show方法,由于Zi类中重写了Fu类中的show方法,把Fu类中的show覆盖,Fu()指向Zi类对象z,调用z中的show方法,且这时num的值为0,故此时输出zi show...0。
(9)Fu()运行结束,弹栈。
(10)回到Zi()构造方法中,继续显示初始化num为8.
(11)Zi()运行结束,弹栈。
(12)实例化完毕,把0X0045赋给z,Zi指向堆内存。
(13)show方法进栈,自带this引用指向0X0045,输出zi show ...8。
(14)show运行完毕,弹栈。
9-4,final关键字
1,继承的弊端:打破了封装性。
2,final关键字:
(1)final是一个修饰符,可以修饰类、方法、变量。
(2)final修饰的类不