Java匿名类重定义核心技术疑问解析
Java匿名类继承父类的核心问题解析
这是一个基于普通类(而非接口)创建匿名子类的Java程序:
class Anonymous { int i = 10; void main() { System.out.println("Anonymous Main " + i); } void setI(int i) { System.out.println("under set"); this.i = i; } void main2() { setI(30); System.out.println("Main 2's " + i); } } public class Runner { public static void main(String[] args) { Anonymous a = new Anonymous() { int i = 20; @Override void setI(int i) { System.out.println("Over set"); this.i = i; } @Override public void main() { setI(0); System.out.println("Redefined Anonymous Main " + i); } }; a.main(); a.main2(); } }
程序输出:
Over set Redefined Anonymous Main 0 Over set Main 2's 10
核心疑问解答
1. 在main2方法中,为何访问到的i值为10?
main2是定义在父类Anonymous里的方法,Java规则里,成员方法访问成员变量时,默认认的是当前方法所属类的变量。main2属于父类,它里面的i直接指向父类的int i = 10,不会去子类找同名变量——哪怕子类有一样名字的i,父类方法的代码也只认自己类里的变量。
2. 为什么对象a存在两个i的成员变量副本?
这个匿名类是Anonymous的子类,子类里声明了和父类同名的int i = 20,这属于成员变量隐藏,不是方法那种重写。父类的成员变量和子类的同名变量会各自占一块内存:父类的i在对象的父类实例区域,子类的i在子类扩展的实例区域,所以整个a对象里就有两个i的副本。
3. 既然存在两个i的副本,为何调用setI时不存在两个setI方法副本?
方法和成员变量的规则完全不一样:子类重写父类的setI后,父类的该方法会被覆盖。Java的动态绑定机制下,通过父类引用调用setI时,会自动执行子类重写后的版本。父类的setI方法虽然还在字节码里,但对于这个子类对象来说,调用时只会触发子类的版本——除非你用super.setI()显式调用父类的方法,否则看不到父类的版本。
4. 为什么main2方法能访问重定义后的setI方法,却无法访问重定义后的i?
- 关于
setI:Java方法调用是动态绑定,不管方法定义在父类还是子类,调用时会根据实际对象的类型(这里是匿名子类)执行对应的重写方法,所以main2里调用setI(30)会触发子类的版本。 - 关于
i:成员变量的访问是静态绑定,编译期就确定了要访问的变量所属的类。main2是父类的方法,编译时就定了它访问的是父类的i,和实际对象的子类类型无关,所以看不到子类的i。
5. Java在底层针对该场景具体执行了什么操作?
从底层实现看:
- 编译器会把这个匿名类编译成独立的字节码文件(比如
Runner$1.class),明确标记它是Anonymous的子类。 - 子类字节码的内存布局里,会同时包含父类的
i和自己的i——父类的实例字段在前,子类的在后,各自占独立内存位置。 - 方法调用时:JVM会通过对象的方法表(Method Table)找到子类重写后的
setI方法地址执行;而main2里访问i时,编译期就计算好了父类i在对象内存中的偏移量,直接读取该位置的值。 - 当用父类引用
Anonymous a指向子类对象时,JVM会维护对象的类型信息,确保动态方法调用能找到正确的子类方法,但变量访问还是按编译时的父类类型来定位。
内容的提问来源于stack exchange,提问作者Razer
相关产品推荐
相关产品推荐

