我们正在谈论Java。从技术上讲,什么解释了创建子级时总是需要调用父级构造函数的事实?例如,我们有两个类:
public class Asset {
public String name;
public Asset(String name) {
this.name = name;
}
}
public class House extends Asset {
public long cost;
public House(String name, long cost) {
this.name = name;
this.cost = cost;
}
}
在 House 中会出现错误“‘Asset’中没有可用的默认构造函数”。为什么子级调用父级构造函数如此重要?在这种特殊情况下,乍一看,这根本没有必要,因为 对于 House,构造函数会自行填写所有字段。
UPD。该问题与评论中提供的任何链接都不重复。没有一个答案\评论\链接回答我的问题,因为答案归结为:
- “要创建子类的实例,您需要顺序调用超类的构造函数。” 但没有写为什么需要这样做。
然而,根据答案中的短语(Upiter 1401 给出的链接),“超类必须在子类之前初始化,以便任何初始值、内存位置等都具有有效的起始值。”,我得出的结论是,我们需要挖掘父子类之间的技术组织关系。例如:
- 子级中父成员的继承在技术上是如何实现的?那些。这是将成员复制到后代的平庸的幕后行为,还是连接内存中两个类的更狡猾的机制。
- 创建子实例时,是否将父类的类加载到内存中。
- 构造函数在创建实例时的技术作用是什么,因为字段是在构造函数执行之前初始化的。因此,这并不是它总是在实例创建过程结束时调用的主要原因。
- ETC。
因为您的字段
Asset.name
是公共的,如果它是私有的,则无法在子类构造函数中对其进行初始化。一般来说,子类的作者不应该考虑如何初始化父类的字段(这可能是由另一个人在另一个时间编写的,等等),并去弄清楚一切是如何工作的,特别是如果有不是一个父类,而是一整个链)。他只需要记住调用可用的父构造函数之一,这将完成所有工作。这需要显式或隐式调用父构造函数。
简而言之,创建子类的对象时,无论如何都必须(显式或隐式)调用父类的构造函数。如果没有显式调用构造函数,仍然会发生不带参数的隐式调用
super()
。因此你需要:
因为这就是 Java 中的实现方式。无论如何,在继承时,都会调用从当前构造函数到“顶级”父级构造函数(一直到类构造函数
java.lang.Object
)的构造函数链。据我了解,您对语言结构原理的逻辑解释感兴趣。
根据定义,构造函数用于将对象初始化为工作一致状态。这意味着在程序可以使用类对象之前,必须 100% 执行其中的关键代码。您仅将构造函数用作这个更大机制的一小部分。
您仅使用它来初始化变量,但您可以执行更多操作:
构造函数还可以确保无论使用您的类的开发人员的意愿如何,都会执行此代码。这是必要的,以免从内部破坏他的逻辑。以及强制私有字段的要求、保护对内部方法的访问的封装等。也就是说,这增加了程序的可靠性,因为普通方法可能会被忘记被调用,或者由于无知而根本不被调用。
第二部分,由于这是父类的关键代码,所以继承时必须调用它。否则,继承只会让它被忽略。这将意味着违反软件可靠性原则。特别是考虑到您可能拥有即使继承人也无法访问的私有字段和方法。您将如何私下检查和处理额外的逻辑?或者如果代码已经在父级中,则连接到设备(复制粘贴是最糟糕的选择)?
第三部分,父类的数据必须初始化并准备好供其余代码使用。子类与其构造函数一样,在使用它之前也必须获取所有数据的正确状态。因此,一开始就调用了父类的构造函数。只有在此之后,程序才能确保您正在使用正确的数据,并在后续程序中补充和使用它。
这还允许 JVM 在内部透明地处理代码,并在必要时将指令放置在它们所属的位置。JVM 开发人员不必考虑数据放置在何处以及如何放置、必须调用什么以及最终不应该调用什么。该语言的实现并不处理您想要在那里编写的内容的逻辑解释;它的任务是清楚地遵循说明。
因此,继承的实现要求父类的关键代码必须在一开始就通过基类的构造函数之一来调用。
UPD
语言规范仅笼统地描述了它应该如何工作,但没有公开其实现。这意味着虚拟机创建者可以完全自由地决定操作。它可以首先创建父类的变量,调用构造函数,然后添加子类的数据。可以一次创建所有变量,然后依次调用两个构造函数。也许在开发过程中你可以完全改变实现。可以使用更聪明的东西。此类问题的答案在于特定开发人员对特定虚拟机的实现,并且可能会随着时间而改变。
因此,通常会在特定的虚拟机(相同的 JVM HotSpot)上查看实现,但需要注意的是,在其他虚拟机中一切可能会有所不同。
当实例化一个类时,Java 执行以下步骤:
OOP 中的封装原则意味着类的实现细节对外部使用是隐藏的。这意味着派生类不应直接初始化从基类继承的字段,因为这会破坏封装。