考虑到从基类继承时示例化的顺序,Java如何知道何时重写类方法?

tzdcorbm  于 2023-06-20  发布在  Java
关注(0)|答案(2)|浏览(80)

我在大学里学习OOP(Java),遇到了以下代码:

// This example program aims at inspecting and understanding the order in which the individual 
// initialization steps are taken when executing this program.

public class ConstructorTest {
    public static void main(String[] args) {
        new Sub();
    }
}

class Super {
    Super() {
        System.out.println("Super constructor");
        m();
    }

    void m() {
        System.out.println("Method m() of class Super");
    }
}

class Sub extends Super {
    Person p = new Person();

    Sub() {
        System.out.println("Sub constructor");
        m();
    }

    @Override
    void m() {
        System.out.println("Method m() of class Sub");
        System.out.println(p.getName());
    }
}

class Person {
    String name;

    String getName() {
        return "John Doe";
    }
}

代码的输出如下:

"Super constructor"
"Method m() of class Sub"
NullPointerException

发生NullPointerException的原因是无法调用p.getName(),因为Person p = new Person()是用null示例化的。(这是故意的!)
程序按以下顺序执行:
1.执行ConstructorTest类中的main方法。

  1. new Sub()被调用,这调用了Sub类的构造函数。
  2. Sub类构造函数开始执行。
    1.由于Sub类扩展了Super类,因此在Sub类构造函数的主体之前调用超类构造函数Super()
    1.在Super类构造函数中:
    a)System.out.println("Super constructor");行将“Super constructor”打印到控制台。
    B)调用方法m()。由于Sub类覆盖了此方法,因此将调用Sub类中被覆盖的版本。但是,此时,Sub类构造函数尚未完成执行。
    1.控制返回到Super类构造函数,构造函数执行完成。
    1.控件返回到Sub类构造函数。
    1.遇到行Person p = new Person();
  3. Person类构造函数被调用,它没有任何显式输出。
    1.创建Person类的示例并将其分配给p示例变量。
    1.控制返回到Sub类构造函数,构造函数执行完成。
    1.执行Sub类构造函数中的System.out.println("Sub constructor");行,将“Sub constructor”打印到控制台。
    1.再次调用m()方法,这次是在Sub类构造函数中。
    1.在Sub类中被重写的m()方法内部:
    a)执行System.out.println("Method m() of class Sub");行,将“Method m()of class Sub”打印到控制台。
    B)执行System.out.println(p.getName());行,它调用Person类的初始化p示例上的getName()方法,并将“John Doe”打印到控制台。
    我的问题是:
    当调用Super类构造函数时,Java首先将System.out.println("Super constructor");打印到控制台。在下一步中,它将调用Super.m()。但是,由于Super.m()Sub类中被覆盖,因此它改为调用Sub.m()。如果示例化的顺序要求在子类构造函数之前调用超类构造函数,那么Java如何知道Sub类覆盖了Super类中的方法m()?它是否递归地检查每个基类方法是否有同名的子类方法?是否在示例化基类之前“扫描”了Sub类,从而Java知道在示例化时遇到基类方法时如何处理它?
    基本上,我想更好地理解当Java重写类方法时,幕后发生了什么。
3qpi33ja

3qpi33ja1#

您假设类元数据的初始化依赖于对象的初始化,但事实并非如此。类在创建该类的第一个示例之前初始化,并且所有示例共享类数据。
例如,当您运行以下程序时

public class Super {
    public void method1() {}
    public void method2() {}
}
public class Sub extends Super {
    @Override public void method2(){}

    public static void main(String[] args) throws NoSuchMethodException {
        System.out.println(Sub.class.getMethod("method1").getDeclaringClass());
        System.out.println(Sub.class.getMethod("method2").getDeclaringClass());
    }
}

会打印出来的

class Super
class Sub

这表明,即使该类的示例不存在,也知道哪个方法被重写了。(类和方法必须是public,本例才能正常工作)
在线演示(使用内部类将两个公共类放在一个文件中)
我们还可以证明,无论构造函数的执行如何,每个对象都是其类的示例:

public class Sub extends Super {
    public static void main(String[] args) throws NoSuchMethodException {
        new Sub();
    }
}
class Super {
    Super() {
        System.out.println("Constructing object of type " + getClass());
    }
}

印刷品

Constructing object of type class Sub

执行new Sub()时,会发生两件事:

  • 首先,为Sub的示例分配内存,内存足够大,可以容纳属于Sub的所有示例字段(包括继承的字段)。对象的元数据被初始化以指示它是Sub的示例(考虑指向类元数据的不可更改的指针),并且所有字段被初始化为其类型的默认值(零、falsenull)。
  • 然后执行构造函数。这包括字段初始化器和初始化器块的执行,这发生在super构造函数调用之后,但在构造函数的任何其他语句之前。

超级构造函数可以调用重写的方法,如前所述,但在构造函数中调用可重写的方法通常不是一个好主意,因为这些重写的方法将看到子类的字段处于其默认状态。

public class Sub extends Super {
    public static void main(String[] args) throws NoSuchMethodException {
        new Sub();
    }

    int value = 10;

    @Override
    void method() {
        System.out.println("value = " + value);
    } 
}
class Super {
    Super() {
        System.out.println("Constructing object of type " + getClass());
        method();
    }
    void method() {}
}

印刷品

Constructing object of type class Sub
value = 0

在线演示

t9aqgxwy

t9aqgxwy2#

还有一个 * 静态初始化 *。这将在编译器需要资源时发生。

在这种情况下,在下面的声明期间。

class Sub extends Super

因此,步骤如下。
1.Sub 构造函数方法从 main 调用
1.Super 静态初始化,因为 Sub 扩展了 Super
1.Sub 静态初始化,因为它总是在类的初始化之前调用
1.Super 构造函数方法,因为 Sub 扩展了 Super
1.最后,m,来自 Sub,因为您的电话来自 Sub

  • "...如果示例化的顺序规定父类构造函数必须在子类构造函数之前调用,那么Java如何知道Sub类覆盖了Super类中的方法m()?..."*

例如,为每个类添加一个 * 静态初始化块 *。

static {
    System.out.println("super, static initialization");
}

输出量

super, static initialization
sub, static initialization
Super constructor
Method m() of class Sub

相关问题