今天,看到《Thinking in Java》中文版中的P162构造器内部的多态方法的行为。中文版啊,前面的两段话我表示很无语。
原话如下:
如果要调用构造方法内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义。然而这个调用的效果可能相当难以预料,因此被覆盖的方法在对象被完全构造之前就会被调用。这可能会造成一些难于发现的隐藏错误。
从概念上讲,构造方法的工作实际上是创建对象。在任何构造方法内部,整个对象可能只是部分形成--我们知道基类已经进行初始化。如果构造方法只是在构建对象过程中的一个步骤,并该对象所属于的类是从构造方法所属的类导出的,那么导出部分在当前构造方法被调用的时刻仍旧是没有被初始化的。然而,一个动态绑定的方法调用却会向外深入到继承层次结构内部,它可以调用子类里的方法。如果我们是在构造方法内部这样做,那么就可能会调用某个方法,而这个方法所操纵的成员可能还未进行初始化。---这肯定会招致灾难。
代码如下:
class Glyph{
void draw(){
System.out.println("Glyph.draw()");
}
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw(){
System.out.println("RoundGlyph.draw() radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String [] args){
new RoundGlyph(5);
}
}
结果如下:
Glyph() before draw()
RoundGlyph.draw() radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
我的理解是:之所以这可能不是我们想要的结果,是因为我们没有真实认识到类的初始化的实际过程。
对于类的初始化过程,我原先的理解是:
1) 调用基类构造函数,初始化基类。
2)按声明顺序调用成员的初始化方法。
3)调用导出类构造器的主体。
而错误的原因正是在调用任何函数之前,在堆栈中已经为你new的对象类分配了存储空间并进行了初始化。
下面就是《Thinking in Java》对此问题的总结
初始化的实际过程是:
1.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。
2.如前所述那样调用基类构造器,此时,调用被覆盖后的draw()方法(注意,要在调用RoundGlyph构造器之前调用),由于步骤1 的缘故,我们此时会发现radius的值为0。
3.按照声明的顺序调用成员的初始化方法。
4调用导出类的构造器主体。
这样做有一个优点,那就是所有东西都至少初始化为零(或者是某些特殊数据类型中与“零”等价的值)而不是仅仅留作垃圾。其中包括通过组合而嵌入一个类内部的对象引用,其值是null,如果忘记为该引用进行初始化,就会在运行时throw NULLPointException。
有一句话很经典:“用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法”。
在构造器内唯一能够安全调用的那些方法是基类的final方法。
分享到:
相关推荐
Java中构造器内部的多态方法的行为实例分析是Java编程语言中一个非常重要的知识点,本文将详细介绍Java中构造器内部的多态方法的行为,结合实例形式分析了java构造器内部多态方法相关原理、功能及操作技巧。...
封装是指将对象的状态(属性)和行为(方法)捆绑在一起,并且隐藏对象的内部实现细节,只暴露必要的接口供外部调用的过程。 **特点:** 1. **隐藏内部实现细节:** 对象的内部状态和实现细节被隐藏起来,用户只能...
当子类继承父类时,需要通过显式调用父类的构造器来初始化父类中的数据成员。这通常通过`super()`关键字完成。 **2.5 多层继承** Java支持多层继承,即一个类可以从另一个类继承,而这个父类本身也可以从另一个...
同时,这些类可能会有私有数据成员,以及公有构造函数和访问器方法,体现封装特性。如果文件中还有其他类,可能是为了展示如何通过继承和多态构建复杂的类层次结构。 通过学习和实践这些例子,开发者能够更好地理解...
此外,Java还支持构造器,用于初始化对象的状态,以及getter和setter方法,实现数据的读取和修改。 **继承**是面向对象编程的另一个关键特性,它允许一个类(子类)从另一个类(父类)继承属性和行为。这有助于代码...
在"第一章 类和对象.pdf"中,会详细介绍如何声明和创建类,包括类的构造器、成员变量和方法。 - **对象**:对象是类的实例,它们是程序运行时的实际参与者。对象通过调用类的方法来执行操作。 2. **对象和封装**:...
构造方法不参与多态,因为它们是类的实例化过程的一部分,与具体的类紧密相关。但是,构造方法可以在子类中被重写以满足特定的初始化需求。 **5.6 内部类** 内部类是Java中一种特殊的形式,它在一个类的内部定义。...
子类构造器可以通过`super`关键字调用父类的构造函数,确保初始化过程正确进行。 6. **内部类与匿名类** 内部类是定义在另一个类内部的类,可以是静态的或非静态的。非静态内部类可以访问外部类的所有成员,包括...
封装是面向对象编程的一个核心概念,它指的是将数据(属性)和操作这些数据的方法(行为)捆绑在一起,隐藏内部实现细节,并对外提供简单的接口来访问这些数据。通过封装可以实现数据的安全性,减少耦合度。 - **...
在局部变量的上下文中,基本类型的变量被final修饰后,必须在声明时或在构造器中明确初始化,并且一旦赋值后就不能再被改变。而对于引用类型的变量,final关键字仅保证引用地址不变,即不能指向另一个对象,但允许...
5. **封装**:面向对象编程的一大原则就是封装,它将数据和操作数据的方法捆绑在一起,隐藏内部实现细节,提供公共接口供外部使用。在成绩管理系统中,每个学生对象应该封装其个人信息和成绩,对外只暴露安全的操作...
在设计抽象类时,应避免在其中定义公共或内部受保护的构造函数,而应提供受保护的或内部的构造函数供子类使用。同时,抽象类可以包含非抽象成员,这些成员提供了默认的实现,使得子类在某些情况下不必完全重写所有...
4. **构造器**:特殊的方法,用于初始化新创建的对象。 5. **getter和setter方法**:提供对类中数据的访问和修改的控制,防止直接暴露内部状态。 ### 二、继承 继承允许一个类(子类)继承另一个类(父类)的属性...
Java知识点主要涵盖面向对象编程的基础概念,包括类、对象、接口、变量、构造器、方法重载、继承、重写以及控制修饰符等核心概念。 1. 面向对象特性: - 高内聚、低耦合:类内的元素紧密关联,对外界影响小。 - ...
- **默认构造器**:如果程序员没有定义构造方法,Java会提供一个无参数的默认构造器。但是,一旦程序员定义了一个构造器,Java就不会再提供默认构造器。 - **带参数的构造器**:根据需要,我们可以定义带有不同...
* 局部变量:声明在方法体、方法的形参、内部类、代码块、构造器中 三、方法 * 方法的声明:权限修饰符 返回值类型 方法名(形参列表){方法体} * 方法的重载:同一个类中,允许存在同名方法,参数个数或类型不同 ...
构造器是一种特殊的方法,主要用于初始化对象。每个类至少有一个构造器,如果没有显式地定义构造器,Java编译器会自动提供一个默认构造器。 1. **构造器的命名规则**:构造器的名称必须与类名完全相同。 2. **构造...
本篇文章将深入探讨Java中的类,包括类的定义、构造器、访问修饰符、成员变量、方法、继承、封装、多态等核心概念。 1. **类的定义** 类是创建对象的蓝图,它定义了对象的状态(数据成员或变量)和行为(成员函数...
运行时多态则主要通过方法重写(Overriding)实现,子类可以重写父类中的虚方法,以提供特定的行为。 3. 抽象类(Abstract Class)和接口(Interface):在C#中,抽象类可以包含抽象方法(没有实现的方法),用于...
每个类至少有一个构造方法,即使未显式声明,系统也会自动生成一个默认的无参构造器。 2. **多态**: - **类型**:多态分为编译时多态(函数重载)和运行时多态(方法覆盖或虚函数)。 - **编译时多态**:通过...