Java子类对象的内存布局

案例

class Father {
    
    
    int num = 111;

    public void fun() {
    
    
        System.out.println("father");
    }
}

class Son extends Father {
    
    
    int num = 222;

    public void fun() {
    
    
        System.out.println("son");
    }
}

这边在Son类重新定义了成员变量的方法。

子类对象内存分配过程

  1. 首先是类加载,需要父类先加载,然后子类加载。
  2. 分配内存
  3. 赋0值
  4. 写对象头
  5. 走init方法

分配内存的问题,到底有几个对象?

调用子类构造器的时候,会先去调用父类的构造器,难道这就意味着,创建子类对象的时候,同时也创建了一份父类对象?也就是说,在堆区有两个对象?

不是的,子类对象会拥有父类定义的所有成员变量,但是,他不一定有父类成员变量的访问权限。可以想象为,子类对象里面套了一个父类的对象,但是本质上堆区只有一个子类对象。
怎么证明呢?我们在Son中输出一下super的类型不就知道了?

class Son extends Father {
    
    
    public void test () {
    
    
    	System.out.println(super.getClass().getName); // 结果为Son
	}
 }

关于权限问题,文章最上面的两个类中都有num成员变量,Son其实拥有两份num,分别可以通过(this.)num和super.num进行访问。因为父类权限没有设置成private,因此子类可以访问。
如果父类中设置为private int num,那么子类调用super.num将会出错。

有人会说,既然父类的private属性子类访问不了,那么还分配给子类对象,不是浪费空间?这种想法是错的,因为我们仍然可以通过父类的public方法对private域进行间接访问。

刚才讲了继承成员变量的问题,那么方法、静态变量呢?

注意,堆区存储的仅仅是成员变量,而方法,静态变量这些都应该是在方法区的(虽然JDK7以后静态变量在堆区,不过是在Class对象内部),和成员变量组成的堆对象不是存储在一起的。堆对象只是一个存储成员变量的一团东西。

所以继承方法,这个严格上来说,其实是动态链接的功劳,在实际运行过程中,某些编译期无法确定的方法调用会进行一个动态链接的动作,最终链接到的方法可能是父类中的方法,也可能是子类中的方法。

  Father son = new Son();
  son.fun();

上述代码优先是调用运行时类型Son的对应方法,找不到才会去父类中找。

所以,当前运行时类型Son如果能找到这个方法,调用效果就等同于重写;如果找不到,调了父类的方法,效果等同于继承。

不要以为调用到了父类方法,他就是在一个父类的对象里面运行的,上面说过了,只会有一个子类对象。方法区的方法可以想象为共享的东西,我们拿这个东西放到子类对象里来使用了一次。这些方法其实不会和调用方类型有任何强绑定。

如果在Father中定义
public void fun() {
    
    
	System.out.println(getClass().getName);
}
然后在子类Son中我们不重写这个方法,按照动态链接,调用到的是父类的方法
son.fun(); // 输出的仍然是Son,也就是虽然调用了父类方法,但是只是拿父类方法在我们子类对象中使用了一下,所在类型还是Son

另外,讲一下编译器解决某些方法调用的问题,编译器的逻辑就是,如果你的编译类型从父类继承来了这个方法、或者你自己声明了这个方法,那么你调用没事;但是如果自己没声明,父类又没声明,那么编译不通过。
举例来说

class Father {
    
    
}

class Son extends Father {
    
    
	public void fun() {
    
    
		System.out.println("son");
	}
}

// 使用
Father son = new Son();
son.fun(); // 编译不通过

上面的对象son的编译类型是Father,但是Father自己没声明fun,Father的父类也没有fun,那么调用fun不合法,编译不通过。

静态变量和上面说的方法重写,有类似的效果,也有类似于动态链接的效果,子类中如果重新声明父类中的静态变量,假如是var,那么son.var就是调用子类中的静态变量,如果子类没有重新声明,那么就是调用父类中的var。
但是如果使用Father.var来调用,这就没有动态链接效果了,因为他是一个明确的调用,也就是Father类的类变量,肯定就是Father中的var。

猜你喜欢

转载自blog.csdn.net/weixin_43696693/article/details/129765569