【问题标题】:Actual use of default constructor in javajava中默认构造函数的实际使用
【发布时间】:2022-01-14 03:49:22
【问题描述】:

当程序员未能将任何构造函数写入类时,编译器会提供默认构造函数。据说这些构造函数是用来初始化类属性的默认值的。但是如果程序员提供了构造函数,那么简单的:

public class Main {
  int a;

  Main() { // user defined simple constructor
    System.out.println("hello");
  }

  public static main(String[] args) {
    Main obj = new Main();
  }
}

在上面的代码中,用户已经包含了一个构造函数。但是它不会初始化实例变量(a)。此外,不会调用默认构造函数。那么变量'a'怎么会被初始化为它的默认值。

如果是这样,默认构造函数不会将类变量初始化为默认值,编译器会自动执行,那么默认构造函数的实际用途是什么?

为什么在用户编写构造函数失败的情况下,编译器会添加默认构造函数?

【问题讨论】:

  • 语言方面,默认构造函数必须存在,语言才有意义并正确编译(如果没有提供其他构造函数)。毕竟,你在写new Main(),所以代码中必须有一个构造函数Main(),即使你没有写一个。此外,它的内容不为空,它也隐式调用了超级构造函数。 super();(本例中为对象)。此外,反射 API 也需要能够访问它(但这是一个高级主题)。
  • @Zabuzard 从技术上讲,您不需要类中的构造函数。有一种奇怪的情况,我不太记得编译器在哪里创建了没有构造函数的合成类,甚至没有默认构造函数或私有构造函数:它们完全不可实例化。
  • Main() 不一定是“默认”构造函数。术语“默认构造函数”通常(据我所知)是指当您在源代码中省略显式构造函数时编译器插入的构造函数。这也称为隐式构造函数。这与无参数构造函数不同,构造函数接受零参数。无参数构造函数存在于两种情况:当您定义显式无参数构造函数时,以及当您根本没有定义构造函数时(在这种情况下,编译器会生成一个)。
  • @Zabuzard “我不太记得了”dredging from the distant past.
  • 是的,我当时确实看到了,了解到JVM支持不可构造类。但是,如果您希望能够编写new Main(),则需要在字节码中添加一个构造函数,因此必须添加它(设计方面)。或者,他们也可以决定在 JVM 中不支持无构造函数,然后将字节码中没有构造函数解释为存在默认构造函数的事实——那么就不必添加它。但他们没有那样设计它。这就是我对这个问题的看法。

标签: java default-constructor


【解决方案1】:

那么变量'a'怎么会被初始化为它的默认值。

因为语言指定将字段初始化为其默认值。具体来说,JLS 4.12.5

程序中的每个变量在使用它的值之前都必须有一个值:

  • 每个类变量、实例变量或数组组件在创建时都会使用默认值进行初始化(第 15.9 节、第 15.10.2 节):
    • ...
    • 对于int类型,默认值为0,即0。
      • ...

即使您确实在构造函数中对其进行了初始化,您也可以预先读取该字段,并观察其默认值。例如:

  Main() { // user defined simple constructor
    System.out.println(a);  // Prints 0
    a = 1;
  }

虽然它在 Java 中大部分情况下对您来说是隐藏的,但 new Main() 做了两件独立的事情(有关更多详细信息,请参阅 JLS 15.9.4,因为它实际上不止两件事情):

  • 它创建一个Main 的实例
  • 然后它调用构造函数来初始化该实例。

将字段初始化为其默认值实际上发生在创建实例时(第一步,如上面 JLS 的引用中所述);因此,即使调用构造函数的第二步没有发生,字段仍会初始化为其默认值。

为什么在用户编写构造函数失败的情况下,编译器会添加默认构造函数?

因为否则您将无法创建该类的实例。

此外,默认构造函数(就像所有第一行不调用this(...) 的构造函数一样)调用超级构造函数。所以,它看起来像:

Main() {
  super();
}

您必须调用类的超级构造函数才能对基类进行必要的初始化。

【讨论】:

  • 我可能是错的,但我将 OP 问题更多地解释为 “如果它没有附加实际的真实逻辑,为什么在字节码中实际添加默认构造函数很有用”。为此添加了一个额外的答案以补充您的答案。
  • @Zabuzard 我想我已经提到过:“另外......”
  • @AndyTurner 您说过构造函数用于创建该类的实例。那么“新”关键字呢?不是那个起作用的吗?
  • 不,我没说过:构造函数是用来初始化类的实例的。新的类实例创建表达式(由限定实例(如果有)、new 关键字、类名、括号和参数组成)导致实例被创建和初始化;你无法真正分离出任何一点。
【解决方案2】:

默认值

据说这些构造函数是用来初始化类属性的默认值的。

这是不正确的。构造函数(包括 default 无参数构造函数)not 将字段初始化为其默认值。这已由语言事先隐式完成(请参阅 JLS 定义)。

默认构造函数等同于完全空的构造函数:

Foo() {}

从技术上讲,与其他构造函数一样,this 隐式仍然包含对父类构造函数的调用:

Foo() {
  super();
}

另外看看public class Foo {}的字节码,也就是:

public class Foo {
  public Foo();
    Code:
       0: aload_0       
       1: invokespecial #1  // Method java/lang/Object."<init>":()V
       4: return        
}

您可以清楚地看到默认构造函数以及调用Objects 构造函数的代码。


为什么要在字节码中添加?

为什么在用户编写构造函数失败的情况下,编译器会添加默认构造函数?

理论上它不必那样做。然而,在语言设计方面,将其添加到简化语言的其余部分要容易得多

例如,那么你不需要任何魔法来使new Foo(); 工作,因为构造函数只是实际存在于 JVM 执行的代码中。

同样适用于更高级的主题,例如 reflection API,它的方法类似于

Object foo = Foo.class.getConstructor().newInstance();

因此,如果构造函数只是实际存在于字节码中,同样,您也确实不需要任何 JVM 中的魔法来完成这项工作。它开箱即用。

最终,开发人员做出了设计决定,以他们的方式创建它。他们也可以有不同的认识。

然而,这样一来,作为语言的 Java 和 JVM 字节码之间就有了一个更更清晰的划分。从技术上讲,您还可以在字节码中创建 甚至根本没有构造函数 的类(您无法从 Java 中创建),这对于编译为 JVM 字节码的特殊工具和其他语言来说很有趣( Kotlin、Groovy、Scala、Clojure 等)。

【讨论】:

    【解决方案3】:

    使用默认值(0、0.0、null、false 等)初始化字段

    【讨论】:

      【解决方案4】:

      默认行为很有用。如果它没有被使用或将它放在另一个类中,或者将它设置为空,则替代方法可能是删除它。但大多数时候,您确实需要默认行为。我相信这是大体的想法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-24
        • 2013-06-08
        • 2015-06-26
        • 1970-01-01
        • 2016-06-05
        相关资源
        最近更新 更多