You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java向上转型代码执行解析:方法重写与变量访问差异及内存分配

向上转型下的方法与变量行为差异:拆解Java的绑定规则与内存分配

先把你的代码格式化后放在这里,方便对照理解:

public class demo { 
    public static void main(String[] args) { 
        aaa bb = new b(); 
        System.out.println(bb.a); 
        int c = bb.eat(); 
        System.out.println(c); 
    } 
} 

class aaa{ 
    int a = 30; 
    int eat() {
        int x = 60; 
        System.out.println("CHEWING"); 
        return x; 
    } 
} 

class b extends aaa{ 
    int a = 23; 
    int eat() { 
        //super.eat(); 
        int x = 70; 
        System.out.println("EaTING"); 
        return x; 
    } 
}

运行这段代码会得到这样的输出:

30
EaTING
70

也就是你遇到的「重写方法走子类逻辑,但变量取父类值」的现象,这本质是Java对成员方法和成员变量采用的绑定规则不同导致的,结合向上转型的特性,我给你一步步拆解:

一、向上转型的执行逻辑核心:静态绑定 vs 动态绑定

当你写aaa bb = new b();时,这就是Java里的向上转型——用父类类型的引用指向子类的实例对象。这时候,Java对方法和变量的处理逻辑完全不一样:

1. 成员方法:动态绑定(运行时决定)

Java的方法调用是运行时绑定的:编译阶段只会检查父类aaa有没有eat()方法(只要存在,编译就通过),但真正运行的时候,JVM会去看实际创建的对象类型(这里是b的实例),然后调用该类中重写的方法。
所以bb.eat()会执行子类b里的eat(),输出"EaTING"并返回70,这就是方法重写带来的多态特性。

2. 成员变量:静态绑定(编译时决定)

和方法完全不同,Java的成员变量是编译时绑定的。编译阶段,编译器只会看引用变量bb的声明类型(也就是aaa),直接把bb.a绑定到父类aaa的a变量上,完全不管实际对象是子类b的实例。
所以System.out.println(bb.a)会输出父类的30——这里子类的a和父类的a是两个完全独立的变量,只是名字相同而已,互相不影响。

二、对应的内存分配机制

要彻底理解这个行为,得看看JVM执行代码时的内存分配细节:

  • 栈内存:存储局部变量bb,它的类型是aaa,里面存的是堆内存中b实例的地址。
  • 堆内存:当执行new b()时,JVM会在堆里创建一个完整的b类实例,这个实例包含两部分:
    • 继承自父类aaa的成员:也就是int a = 30(父类的实例变量);
    • 子类b自己的成员:也就是int a = 23(子类的实例变量)。
      这两个a在堆里是不同的内存位置,只是名字相同,互相独立。
  • 方法区:存储aaa和b的类信息,包括各自的eat()方法字节码。当调用方法时,JVM会根据对象的实际类型,去方法区找到对应的方法执行。

简单总结:堆里的b实例同时带着父类和子类的a,但因为bb是父类类型引用,编译时就定死了取父类的a;而方法调用是运行时看实际对象,所以走子类的eat()。

如果想访问子类的a,你可以把引用强制转回子类类型:((b)bb).a,这样就能拿到23了。

内容的提问来源于stack exchange,提问作者Sri Lalitha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:06:08