当子类没有在Java中定义构造函数时会发生什么?

时间:2009-12-10 03:30:37

标签: java generics oop

我有几个我不知道的案例。首先,如果你没有构造函数:

class NoCons { int x; }

当我执行new NoCons()时,将调用默认构造函数。它究竟做了什么?它是否将x设置为0,还是将其设置为其他地方?

如果我遇到这种情况该怎么办:

class NoCons2 extends NoCons { int y; }

当我致电new NoCons2()时会发生什么? NoCons的默认构造函数是否被调用,然后是NoCons2的构造函数?他们是否各自将xy字段设置为0?

这个版本怎么样:

class Cons2 extends NoCons { int y; public Cons2() {} }

现在我有一个构造函数,但它不会调用超类的构造函数。 x如何初始化?如果我遇到这种情况怎么办?

class Cons { int x; public Cons() {} }
class NoCons2 extends Cons { int y;  }

是否会调用Cons构造函数?

我可以尝试所有这些示例,但我无法确定何时运行默认构造函数。考虑这一点的一般方法是什么,以便我知道未来情况会发生什么?

4 个答案:

答案 0 :(得分:13)

当Java类没有显式定义构造函数时,会添加一个公共的no-args默认构造函数,所以:

class Cons { int x; } 

相当于:

class Cons { int x; public Cons() {} }

子类的构造函数没有显式定义它调用的父构造函数中的哪一个将自动调用父类中的默认构造函数之前执行其他任何操作。所以假设:

class A { public A() { System.out.println("A"); } }

然后这个:

class B extends A { public B() { System.out.println("B"); } }

完全等同于:

class B extends A { public B() { super(); System.out.println("B"); } }

,两种情况下的输出都是:

A
B

所以当你这样做时:

new NoCons2();

订单是:

  1. NoCons的默认构造函数被调用,虽然这在技术上是(2)的第一部分;然后
  2. NoCons2的默认构造函数。

答案 1 :(得分:1)

您想引用Java Language Specification section 12.5 Creation of New Class Instances来获取对象创建的官方规则。相关部分是:

  

在作为结果返回对新创建的对象的引用之前,处理指示的构造函数以使用以下过程初始化新对象:

     
      
  1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。
  2.   
  3. 如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用开始(使用此构造函数),则使用这五个相同的步骤计算参数并以递归方式处理该构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同;否则,继续执行第5步。
  4.   
  5. 此构造函数不以同一个类中的另一个构造函数的显式构造函数调用开头(使用此方法)。如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。使用这五个相同的步骤评估参数并递归处理超类构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同。否则,请继续执行步骤4.
  6.   
  7. 为此类执行实例初始化程序和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,在这些顺序中,它们以文本方式显示在类的源代码中。如果执行任何这些初始值设定项导致异常,则不会处理其他初始化程序,并且此过程会突然完成同样的异常。否则,继续执行步骤5.(在某些早期实现中,如果字段初始化表达式是一个常量表达式,其值等于其类型的默认初始化值,则编译器会错误地省略代码以初始化字段。)
  8.   
  9. 执行此构造函数的其余部分。如果执行突然完成,则此过程突然完成,原因相同。否则,此过程正常完成。
  10.   

因此,在您的示例中,如果类定义中未提供构造函数,则会为您插入默认构造函数。当你写

new NoCons2();
  1. 首先调用超级构造函数(为super()调用,因为你没有显式调用)。
  2. 正在构建的类的实例变量已初始化。
  3. 执行构造函数体的其余部分(在您的情况下没有任何内容)。
  4. 在第一个示例中,x将在构造NoCons期间设置,y将在构造NoCons2期间设置。

    因此,该示例中事件的确切顺序如下:

    1. NoCons2构造函数名为。
    2. 致电super(),转到3
    3. NoCons构造函数调用。
    4. 调用super(),这是对Object()的隐式调用。
    5. 无论Object构造函数发生什么。
    6. x设置为0.
    7. 完成NoCons构造函数的主体,将控制权返回给NoCons2构造函数。
    8. y设置为0.
    9. 完成NoCons2构造函数的主体
    10. NoCons2对象构建完成。

答案 2 :(得分:0)

克莱图斯回答了最大的问题。另一个答案是Java中的成员变量初始化为0,null或false(取决于类型)。

答案 3 :(得分:0)

这基本上是在调用“new”时会发生什么:

  • 分配内存(足以容纳类的所有数据成员,以及所有父类和一些内务处理信息)
  • 分配的内存设置为零(表示0,0.0,false,null,具体取决于类型)
  • 为调用“new”之后的类调用构造函数。
  • 发生更多事情(下一部分之后)

如果您不提供构造函数,编译器将执行以下操作:

  • 创建一个无参数构造函数
  • 创建的构造函数具有与类(公共或包)相同的访问权限
  • 调用super()。

因此,当调用“new”之后的类的构造函数时,它首先要调用“super()”来调用父构造函数。这种情况一直发生在java.lang.Object上。

在运行构造函数体之前,VM会执行以下操作:

  • 为其指定值的实例变量
  • 然后运行实例初始化程序块(如果存在)。

以下代码显示了所有这些:

public class Main
{
    private Main()
    {
    }

    public static void main(final String[] args)
    {
        final Foo fooA;
        final Foo fooB;

        fooA = new Foo(7);
        System.out.println("---------------------");
        fooB = new Foo(42);
    }
}

class Bar
{
    protected int valueA = getValue("valueA", 1);
    protected int valueB;

    static
    {
        System.out.println("static block for Bar happens only one time");
    }

    {
        System.out.println("instance block for Bar happens one time for each new Bar");
        System.out.println("valueA = " + valueA);
        System.out.println("valueB = " + valueB);
    }

    Bar()
    {
        super();  // compiler adds this - you would not type it in
        System.out.println("running Bar()");
        System.out.println("valueA = " + valueA);
        System.out.println("valueB = " + valueB);
        valueB = getValue("valueB", 2);
    }

    protected static int getValue(final String name, final int val)
    {
        System.out.println("setting " + name + " to " + val);
        return (val);
    }
}

class Foo
    extends Bar
{
    protected int valueC = getValue("valueC", 1);
    protected int valueD;

    static
    {
        System.out.println("static block for Foo happens only one time");
    }

    {
        System.out.println("instance block for Foo happens one time for each new Foo");
        System.out.println("valueC = " + valueC);
        System.out.println("valueD = " + valueD);
    }

    Foo(final int val)
    {
        super();  // compiler adds this - you would not type it in
        System.out.println("running Foo(int)");
        System.out.println("valueC = " + valueC);
        System.out.println("valueD = " + valueD);
        valueD = getValue("valueD", val);
    }
}