Java为了减小复杂性,类取消了多继承,只有单继承。很多同学都会使用继承,无非是使用关键字extends。但大家知道更深层的东西吗?比如在内存空间继承的类和被继承的类怎样存储,有什么关系?大家都知道如果被继承的类没有空构造器即只有有参构造器,那么继承类中构造器第一行必须调用有参构造。那么为什么一定要用super(有参(或无参))?本文将会通过一个例子详细解析类在继承中内存空间的变化。
public class Animal {
String eye;
String mouth;
String nose;
public Animal() {
super();
}
public Animal(String eye, String mouth, String nose) {
super();
this.eye = eye;
this.mouth = mouth;
this.nose = nose;
System.out.println("初始化Animal类");
}
public void watch() {
System.out.println("动物使用" + eye + "看东西");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.wang();
dog.watch();
dog.lactation();
}
}
class Mammal extends Animal {
String breast;
public Mammal() {
super();
}
public Mammal(String breast) {
super("眼睛", "嘴巴", "鼻子");
this.breast = breast;
System.out.println("初始化Mammal类");
}
/**
* 哺乳
*/
public void lactation() {
System.out.println("哺乳动物喂养孩子");
}
}
class Dog extends Mammal {
public Dog() {
super("狗奶");
System.out.println("初始化Dog类");
}
public void wang() {
System.out.println("我可以“旺旺”叫");
}
}
在这段程序中我写了三个类,分别是Animal、Mammal和Dog类,并且一个继承一个,Dog继承Mammal,Mammal继承Animal。那Animal还有没有继承谁呢?大家都知道在Java中所有的类都直接或间接继承Object类,所以实际上Animal还会继承Object,只不过这一切JVM会主动加上,无需我们添加。下面我们通过一张关系图来了解这四个类(包括Object类)之间的内存关系。
在Java类与对象的地址空间分析这篇中已经说过,对象都存储在堆中,没有看过的同学建议先看一下,因为有一些内容相关,比如方法的地址指向。言归正传,在开始Dog dog = new Dog();创建一个Dog对象的时候,先会调用DOg的构造器,Dog类构造器第一句super("狗奶");又会调用父类的有参构造器,这里其父类是Mammal类,进入Mammal类有参构造器后,又会执行super("眼睛", "嘴巴", "鼻子");这一句,所以又会调用Mammal类的父类的有参构造器即Animal类的有参构造器,进入Animal后还会执行super();这一句仍然是调用父类构造器,Animal类还有父类吗?对,是Object类,进入Object类的构造器中便不会再执行super();因为Object类没有父类,他是一切类的老祖宗。到这里后,便会最先在堆中创建Object类的对象,Object类的属性也会得到初始化。得到Object类对象后,便会回到Animal类构造器中。
public Animal(String eye, String mouth, String nose) {
super();
this.eye = eye;
this.mouth = mouth;
this.nose = nose;
System.out.println("初始化Animal类");
}
接着初始化Animal类的属性,并且打印"初始化Animal类",这样也会在堆中生成一个Animal类的对象,而且这个对象并不会重新创建,而是会如上面关系图那样,包裹Object类对象创建了一个Animal类的对象,那么Object类的对象是否消失了呢?并不会,它还存在。我们可以通过super来调用它,比如调用Object类的equal()方法可以通过super.equal()调用。得到Animal类对象后便会回到Mammal类的构造器。
public Mammal(String breast) {
super("眼睛", "嘴巴", "鼻子");
this.breast = breast;
System.out.println("初始化Mammal类");
}
Mammal类的breast属性得到初始化,接下来便会打印“初始化Mammal类”,这样也会得到一个Mammal类的对象。同上理,这个对象不会重新创建,也是包裹Animal类的对象,得到这个对象后便又会回到Dog类。 public Dog() {
super("狗奶");
System.out.println("初始化Dog类");
}
接着便会打印“初始化Dog类”并且生成了Dog类的对象,并且会把这个对象的首地址赋给dog这个变量,到这里就已经执行完main方法中的第一句代码,获得了一个指向Dog类对象的变量dog。按照上述的逻辑,打印的顺序是最先打印"初始化Animal类",最后打印“初始化Dog类”,实际运行中也是如此。综上,我们可知知道所有的类的对象都会拥有一个Object类的对象,那么也会拥有Object类方法,所以不要再疑惑equal()、toString()等方法哪里来了。
最后提醒大家,除了Object类,JVM对每一个类的方法都会传两个参数,分别是super和this,super指向父类对象,this指向当前类对象。所以不要再疑惑super和this这两个东西怎么来的。事实在,在内存空间里,这连个东西都是父类或自身对象的首地址。