- 当一个对象在 Java 中被实例化时,真正进入内存的是什么?
- 是否包含父构造函数的副本?
- 为什么隐藏数据成员在强制转换时的行为与被覆盖的方法不同?
我理解通常为让您正确使用这些东西而给出的抽象解释,但 JVM 是如何真正做到这一点的。
当一个对象被实例化时,只有非静态数据实际上是“创建的”,以及对创建它的对象类型的引用。
没有任何方法被复制。
对创建它的类的“引用”实际上是一个指针调度表。每个类可用的方法都有一个指针。指针始终指向方法的“正确”(通常是对象树中最低/最具体的)实现。
这样,如果您对另一个方法进行了顶级调用,但另一个方法已被覆盖,则将调用被覆盖的方法,因为那是表中指针指向的位置。由于这种机制,调用被覆盖的方法应该不会比调用顶级方法花费更多的时间。
指针表+成员变量是一个类的“实例”。
变量问题与完全不同的机制“名称空间”有关。变量根本不是“子类”(它们不进入调度表),但公共或受保护的变量可以被局部变量隐藏。这一切都是由编译器在编译时完成的,与您的运行时对象实例无关。编译器确定您真正想要的对象,并将对该对象的引用填充到您的代码中。
范围规则通常倾向于“最近”变量。任何更远的同名将被忽略(阴影),有利于更近的定义。
如果您有兴趣,可以更具体地了解内存分配:所有“对象”都分配在“堆”上(实际上比真正的堆更高效、更美观,但概念相同。)变量始终是指针——Java永远不会复制对象,您总是复制指向该对象的指针。方法参数和局部变量的变量指针分配是在栈上完成的,但是即使变量(指针)是在栈上创建的,它们所指向的对象仍然永远不会在栈上分配。
我很想写一个例子,但这已经太长了。如果您希望我输入几个具有扩展关系的类以及它们的方法和数据如何影响生成的代码,我可以......问一下。
我想你会发现这是一个全面的例子:
http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html
从堆中分配内存以保存对象及其超类的所有实例变量和特定于实现的数据。特定于实现的数据包括指向类和方法数据的指针。
对象的实例变量被初始化为其默认值。
最派生类的构造函数被调用。构造函数做的第一件事就是调用大写的构造函数。这个过程一直持续到调用 java.lang.Object 的构造函数,因为 java.lang.Object 是 java 中所有对象的基类。
在执行构造函数的主体之前,会执行所有实例变量初始化程序和初始化块。然后执行构造函数的主体。因此,基类的构造函数首先完成,最派生类的构造函数最后完成。