Java中的子对象是如何构造的?

How is a child object constructed in Java?

在java中,子对象是如何构造的? 刚开始继承,有几点不是很清楚:

子对象是只依赖子class'的构造函数,还是也依赖父对象的构造函数?我需要关于那一点的一些细节。

此外,super() 是否总是在子构造函数中默认调用?

欢迎提供有关此主题的任何其他信息。

  • 在继承中,一个childobject的构造依赖于至少一个parent构造函数。
  • 调用super()方法不是必须的。默认情况下,Java 将调用不带参数的 parent 构造函数,除非您精确定义自定义构造函数。
  • 举个例子

妈妈

public class Mother {

int a;

public Mother() {
    System.out.println("Mother without argument");
    a = 1;
}

public Mother(int a) {
    System.out.println("Mother with argument");
    this.a = a;
}

}

child

 public class Child extends Mother {

public Child() {
    System.out.println("Child without argument");
}

public Child(int a) {
    super(a);
    System.out.println("Child with argument");
}

}

如果你这样做:

Child c1 = new Child();

您将获得:

Mother without argument
Child without argument

如果你这样做:

Child c1 = new Child(a);

您将获得:

Mother with argument
Child with argument

但是,如果将第二个 child 构造函数更改为并删除 super(arg),则将调用不带参数的 parent 构造函数:

    public Child(int a) {
    //        super(a);
    System.out.println("Child with argument");
    }

您将获得:

Mother without argument
Child with argument

我认为“A child object”不是一个很好的思考方式。

您正在制作 object。和所有object一样,它是一些特定class的实例,(毕竟new SomeInterface()不编译)和(几乎)所有object一样,它是因为一些代码某处(当然不一定是你的代码)运行某处java表达式new SomeSpecificClass(args);

我们可以说它是 'child object' 因为 SomeSpecificClass 是 child class 的 class.

但这没什么用。这意味着制作新 'non-child' object 的唯一方法是编写 new Object(); - 毕竟,除了 java.lang.Object 之外的所有 classes 都是 child class:如果你写 public class Foo {},java 的解释和你写 public class Foo extends java.lang.Object {} 完全一样。

因此,除非无用*无关紧要,所有 object 都是 child object,因此作为一个术语,'child object',我不会用那个。

也就是说ALLobject创作就是这样'okay and in what order and how do the constructors work'歌舞套路

它是如何工作的可能最容易通过脱糖来解释。如果您选择忽略它们,Javac(编译器)会注入一些东西,因为在 class 文件/JVM 级别,很多东西感觉是可选的(例如构造函数、超级调用或扩展子句),不是**。

Sugar #1 - 扩展子句

已涵盖:如果您的 class def 中没有 extends 子句,javac 会为您注入 extends java.lang.Object

Sugar #2 - 构造函数中没有超级调用

一个构造函数必须在它的第一行调用一些特定的超级构造函数,,它必须在它的第一行调用同一个class中的一些其他构造函数第一行 (this(arg1, arg2);)。如果你不这样做,java 会为你注入:

public MyClass(String arg) { this.arg = arg; }
// is treated as:
public MyClass(String arg) {
    super();
    this.arg = arg;
}

如果您的 parent class 没有可用的 zero-arg 构造函数,则特别包括编译器错误。

糖#3:没有构造函数

如果您编写的 class 没有构造函数,那么 java 会为您创建一个:

public YourClass() {}

它将是 public,它没有参数,也没有代码。然而,根据 sugar #2 规则,这会进一步扩展到:

public YourClass() {super();}

字段初始化和代码块被重写为一个块。

当您创建新的 object 时,构造函数并不是 运行 的唯一内容。想象一下这段代码:

public class Example {
    private final long now = System.currentTimeMillis();
}

此代码有效;你可以编译它。您可以创建 Example 的新实例,并且 now 字段将保存您调用 new Example() 时的时间。那它是如何工作的呢?感觉 很多 像构造函数代码,不是吗?

好吧,它是这样工作的:从上到下浏览源文件,找到每个 non-static 初始化代码,你可以找到:

public class Example {
    int x = foo(); // all non-constant initial values count
    {
        x = 10;
        // this bizarre constructor is legal java, and also
        // counts as an initializer.
    }
}

然后将所有内容移至 classes 获得的唯一初始化程序,按照您看到它们的顺序。

正在订购

因此,通过 sugar 规则,我们减少了所有 classes 以遵守以下规则:

  1. 所有 classes 都有 parent class.
  2. 所有 classes 至少有 1 个构造函数。
  3. 所有构造函数调用另一个构造函数或来自 parent 的构造函数。
  4. 有一个'initializer'代码块。

现在唯一的问题是,执行的顺序是什么?

答案疯狂。抓住你的帽子。

这是顺序:

首先,将所有字段设置为整个 'construct' 的 0/false/null(构造涉及从 Child 一直到 Object 的每个字段,当然)。

从在 Child 上调用的实际构造函数开始。 运行直接,也就是说,从第一行开始,必然this()super()调用。

评估整行,特别是评估作为参数传递的所有表达式。即使这些本身就是对其他方法的调用。但是,javac 会做一些小的努力来阻止您访问您的字段(因为这些 all 未初始化!我还没有提到初始化器!!)。

是的,真的。这意味着:

public class Example {
    private final long x = System.currentTimeMillis();

    public Example() {
        super(x); // x will be .... 0
        // how's that for 'final'?
    }
}

这将最终调用您的其他构造函数的第一行(它本身也是 this()super() 调用)。要么我们永远无法离开这片森林,并且堆栈溢出错误中止了我们创建这个 object 的尝试(因为我们有一个无休止地相互调用的构造函数循环),或者在某个时候,我们 运行 进入 super() 调用,这意味着我们现在转到 parent class 并再次重复整个歌舞例程。

我们继续前进,一直到 java.lang.Object,通过硬编码,它根本没有 this()super() 调用,并且是唯一一个调用。

那么,我们先停下来。现在的工作是 运行 j.l.Object 的构造函数中的其余代码,但是 fist,我们 运行 Object 的初始化程序。

然后,object 的构造函数 运行 包含其中的所有其余代码。

那么,Parent的初始化器是运行。然后是使用的 parent 构造函数的其余部分。如果 parent 一直在横向移动(this() 在其构造函数中调用),那么这些都是 运行 在方法调用中正常的相反顺序。

我们终于在 Child 结束了;它的初始值设定项运行,然后是构造函数运行,最后我们完成了。

告诉我!

class Parent {
    /* some utility methods so we can run this stuff */
    static int print(String in) {
        System.out.println("@" + in);
        return 0;

        // we use this to observe the flow.
        // as this is a static method it has no bearing on constructor calls.
    }

    public static void main(String[] args) {
        new Child(1, 2);
    }

    /* actual relevant code follows */
    Parent(int arg) {
        print("Parent-ctr");
        print("the result of getNow: " + getNow());
    }
    int y = print("Parent-init");

    long getNow() { return 10; }
}

class Child extends Parent {
    Child(int a, int b) {
        this(print("Child-ctr1-firstline"));
        print("Child-ctr1-secondline");
    }

    int x = print("Child-init");

    Child(int a) {
        super(print("Child-ctr2-firstline"));
        print("Child-ctr2-secondline");
    }

    final long now = System.currentTimeMillis();

    @Override long getNow() { return now; }
}

现在是伟大的益智游戏。应用上述规则并尝试弄清楚这将打印什么。

@Child-ctr1-firstline
@Child-ctr2-firstline
@Parent-init
@Parent-ctr
@getNow 的结果:0
@Child-init
@Child-ctr2-二线
@Child-ctr1-secondline

  • 构造函数的执行顺序是有效的:第一行在前,其余的在最后。
  • 最后一个字段是 0,尽管看起来它永远不应该是 0。
  • 你总是以 运行 宁你的 parent 的构造函数结束。

--

*) 您可以将它们用于锁或标记指针值。假设 'mostly useless'.

**) 你可以破解一个 class 文件,这样它描述的 class 没有 parent class(甚至 j.l.Object); java.lang.Object 的 class 文件就是这样工作的。但是你不能javac做这个,你必须把它拼凑起来,这样的东西会很疯狂,没有真正有用的目的。