Java 为什么 this() 和 super() 必须是构造函数中的第一条语句?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1168345/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 00:28:49  来源:igfitidea点击:

Why do this() and super() have to be the first statement in a constructor?

javaconstructor

提问by Joe Daley

Java requires that if you call this() or super() in a constructor, it must be the first statement. Why?

Java 要求,如果在构造函数中调用 this() 或 super(),它必须是第一条语句。为什么?

For example:

例如:

public class MyClass {
    public MyClass(int x) {}
}

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        int c = a + b;
        super(c);  // COMPILE ERROR
    }
}

The Sun compiler says "call to super must be first statement in constructor". The Eclipse compiler says "Constructor call must be the first statement in a constructor".

Sun 编译器说“调用 super 必须是构造函数中的第一条语句”。Eclipse 编译器说“构造函数调用必须是构造函数中的第一条语句”。

However, you can get around this by re-arranging the code a little bit:

但是,您可以通过稍微重新排列代码来解决此问题:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        super(a + b);  // OK
    }
}

Here is another example:

这是另一个例子:

public class MyClass {
    public MyClass(List list) {}
}

public class MySubClassA extends MyClass {
    public MySubClassA(Object item) {
        // Create a list that contains the item, and pass the list to super
        List list = new ArrayList();
        list.add(item);
        super(list);  // COMPILE ERROR
    }
}

public class MySubClassB extends MyClass {
    public MySubClassB(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(Arrays.asList(new Object[] { item }));  // OK
    }
}

So, it is not stopping you from executing logicbefore the call to super. It is just stopping you from executing logic that you can't fit into a single expression.

因此,它不会阻止您在调用 super 之前执行逻辑。它只是阻止您执行无法放入单个表达式的逻辑。

There are similar rules for calling this(). The compiler says "call to this must be first statement in constructor".

调用 也有类似的规则this()。编译器说“调用 this 必须是构造函数中的第一条语句”。

Why does the compiler have these restrictions? Can you give a code example where, if the compiler did not have this restriction, something bad would happen?

为什么编译器有这些限制?你能给出一个代码示例,如果编译器没有这个限制,会发生什么不好的事情吗?

回答by anio

The parent class' constructorneeds to be called before the subclass' constructor. This will ensure that if you call any methods on the parent class in your constructor, the parent class has already been set up correctly.

父类'constructor需要在子类' 之前调用constructor。这将确保如果您在构造函数中调用父类上的任何方法,则父类已经正确设置。

What you are trying to do, pass args to the super constructor is perfectly legal, you just need to construct those args inline as you are doing, or pass them in to your constructor and then pass them to super:

您正在尝试做的将 args 传递给超级构造函数是完全合法的,您只需要在执行时内联构造这些 args,或者将它们传递给您的构造函数,然后将它们传递给super

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                super(myArray);
        }
}

If the compiler did not enforce this you could do this:

如果编译器没有强制执行此操作,您可以执行以下操作:

public MySubClassB extends MyClass {
        public MySubClassB(Object[] myArray) {
                someMethodOnSuper(); //ERROR super not yet constructed
                super(myArray);
        }
}

In cases where a parentclass has a default constructor the call to super is inserted for you automatically by the compiler. Since every class in Java inherits from Object, objects constructor must be called somehow and it must be executed first. The automatic insertion of super() by the compiler allows this. Enforcing super to appear first, enforces that constructor bodies are executed in the correct order which would be: Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

parent类具有默认构造函数的情况下,会自动为您插入对 super 的调用compiler。由于 Java 中的每个类都继承自Object,因此必须以某种方式调用对象构造函数,并且必须首先执行它。编译器自动插入 super() 允许这样做。强制 super 首先出现,强制构造函数体以正确的顺序执行,即:对象 -> 父 -> 子 -> ChildOfChild -> SoOnSoForth

回答by Jason S

I am fairly sure (those familiar with the Java Specification chime in) that it is to prevent you from (a) being allowed to use a partially-constructed object, and (b), forcing the parent class's constructor to construct on a "fresh" object.

我相当肯定(那些熟悉 Java 规范的人)这是为了防止您(a)被允许使用部分构造的对象,以及(b)强制父类的构造函数在“新的“ 目的。

Some examples of a "bad" thing would be:

“坏”事情的一些例子是:

class Thing
{
    final int x;
    Thing(int x) { this.x = x; }
}

class Bad1 extends Thing
{
    final int z;
    Bad1(int x, int y)
    {
        this.z = this.x + this.y; // WHOOPS! x hasn't been set yet
        super(x);
    }        
}

class Bad2 extends Thing
{
    final int y;
    Bad2(int x, int y)
    {
        this.x = 33;
        this.y = y; 
        super(x); // WHOOPS! x is supposed to be final
    }        
}

回答by Savvas Dalkitsis

You can use anonymous initializer blocks to initialize fields in the child before calling it's constructor. This example will demonstrate :

在调用它的构造函数之前,您可以使用匿名初始化块来初始化子中的字段。此示例将演示:

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

class Parent {
    public Parent() {
        System.out.println("In parent");
    }
}

class Child extends Parent {

    {
        System.out.println("In initializer");
    }

    public Child() {
        super();
        System.out.println("In child");
    }
}

This will output :

这将输出:

In parent
In initializer
In child

在父级
在初始值设定项
在子级

回答by Tom Hawtin - tackline

Because the JLS says so. Could the JLS be changed in a compatible manner to allow it?Yup.

因为 JLS 是这么说的。能否以兼容的方式更改 JLS 以允许它?是的。

However, it would complicate the language spec, which is already more than complicated enough. It wouldn't be a highly useful thing to do and there are ways around it (call another constructor with the result of a static method or lambda expression this(fn())- the method is called before the other constructor, and hence also the super constructor). So the power to weight ratio of doing the change is unfavourable.

然而,它会使语言规范复杂化,这已经足够复杂了。这不会是一件非常有用的事情,并且有一些方法可以解决它(使用静态方法或 lambda 表达式的结果调用另一个构造函数this(fn())- 该方法在另一个构造函数之前调用,因此也是超级构造函数)。所以做改变的功率重量比是不利的。

Note that this rule alone does not prevent use of fields before the super class has completed construction.

请注意,仅此规则并不能阻止在超类完成构造之前使用字段。

Consider these illegal examples.

考虑这些非法的例子。

super(this.x = 5);

super(this.fn());

super(fn());

super(x);

super(this instanceof SubClass);
// this.getClass() would be /really/ useful sometimes.

This example is legal, but "wrong".

这个例子是合法的,但“错误”。

class MyBase {
    MyBase() {
        fn();
    }
    abstract void fn();
}
class MyDerived extends MyBase {
    void fn() {
       // ???
    }
}

In the above example, if MyDerived.fnrequired arguments from the MyDerivedconstructor they would need to be sleazed through with a ThreadLocal. ;(

在上面的示例中,如果MyDerived.fn需要来自MyDerived构造函数的参数,则需要使用ThreadLocal. ;(

Incidentally, since Java 1.4, the synthetic field that contains the outer thisis assigned before inner classes super constructor is called. This caused peculiar NullPointerExceptionevents in code compiled to target earlier versions.

顺便提一下,从 Java 1.4 开始,包含外部的合成字段this在调用内部类的超构造函数之前分配。这会导致NullPointerException针对早期版本编译的代码中出现特殊事件。

Note also, in the presence of unsafe publication, construction can be viewed reordered by other threads, unless precautions are made.

另请注意,在存在不安全发布的情况下,除非采取预防措施,否则其他线程可以重新排序构造。

Edit March 2018:In message Records: construction and validationOracle is suggesting this restriction be removed (but unlike C#, thiswill be definitely unassigned(DU) before constructor chaining).

20183 月编辑:在消息记录:构造和验证中,Oracle 建议删除此限制(但与 C# 不同,在构造函数链接之前肯定this会取消分配(DU))。

Historically, this() or super() must be first in a constructor. This restriction was never popular, and perceived as arbitrary. There were a number of subtle reasons, including the verification of invokespecial, that contributed to this restriction. Over the years, we've addressed these at the VM level, to the point where it becomes practical to consider lifting this restriction, not just for records, but for all constructors.

从历史上看, this() 或 super() 必须在构造函数中排在第一位。这种限制从未流行过,并且被认为是任意的。造成这种限制的原因有很多,包括对 invokespecial 的验证。多年来,我们已经在 VM 级别解决了这些问题,以至于考虑取消此限制变得切实可行,不仅针对记录,而且针对所有构造函数。

回答by DaveFar

I totally agree, the restrictions are too strong. Using a static helper method (as Tom Hawtin - tackline suggested) or shoving all "pre-super() computations" into a single expression in the parameter is not always possible, e.g.:

我完全同意,限制太强了。使用静态辅助方法(如 Tom Hawtin - Tackline 建议的)或将所有“pre-super() 计算”推入参数中的单个表达式并不总是可行的,例如:

class Sup {
    public Sup(final int x_) { 
        //cheap constructor 
    }
    public Sup(final Sup sup_) { 
        //expensive copy constructor 
    }
}

class Sub extends Sup {
    private int x;
    public Sub(final Sub aSub) {
        /* for aSub with aSub.x == 0, 
         * the expensive copy constructor is unnecessary:
         */

         /* if (aSub.x == 0) { 
          *    super(0);
          * } else {
          *    super(aSub);
          * } 
          * above gives error since if-construct before super() is not allowed.
          */

        /* super((aSub.x == 0) ? 0 : aSub); 
         * above gives error since the ?-operator's type is Object
         */

        super(aSub); // much slower :(  

        // further initialization of aSub
    }
}

Using an "object not yet constructed" exception, as Carson Myers suggested, would help, but checking this during each object construction would slow down execution. I would favor a Java compiler that makes a better differentiation (instead of inconsequently forbidding an if-statement but allowing the ?-operator within the parameter), even if this complicates the language spec.

正如 Carson Myers 建议的那样,使用“尚未构造的对象”异常会有所帮助,但在每个对象构造期间检查此异常会减慢执行速度。我更喜欢 Java 编译器,它可以做出更好的区分(而不是无缘无故地禁止 if 语句但允许参数中的 ? 运算符),即使这会使语言规范复杂化。

回答by Kate Gregory

You asked why, and the other answers, imo, don't really say why it's ok to call your super's constructor, but only if it's the very first line. The reason is that you're not really callingthe constructor. In C++, the equivalent syntax is

你问了为什么,其他答案,imo,并没有真正说明为什么可以调用 super 的构造函数,但前提是它是第一行。原因是您并没有真正调用构造函数。在 C++ 中,等效的语法是

MySubClass: MyClass {

public:

 MySubClass(int a, int b): MyClass(a+b)
 {
 }

};

When you see the initializer clause on its own like that, before the open brace, you know it's special. It runs before any of the rest of the constructor runs and in fact before any of the member variables are initialized. It's not that different for Java. There's a way to get some code (other constructors) to run before the constructor really starts, before any members of the subclass are initialized. And that way is to put the "call" (eg super) on the very first line. (In a way, that superor thisis kind of before the first open brace, even though you type it after, because it will be executed before you get to the point that everything is fully constructed.) Any other code after the open brace (like int c = a + b;) makes the compiler say "oh, ok, no other constructors, we can initialize everything then." So it runs off and initializes your super class and your members and whatnot and then starts executing the code after the open brace.

当你看到这样的初始化子句,在大括号之前,你知道它很特别。它在构造函数的任何其余部分运行之前运行,实际上在任何成员变量被初始化之前运行。对于 Java 来说并没有什么不同。在构造函数真正启动之前,在子类的任何成员被初始化之前,有一种方法可以让一些代码(其他构造函数)运行。这种方法是将“呼叫”(例如super)放在第一行。(在某种程度上,那个superorthis有点在第一个大括号之前,即使你在后面输入它,因为它会在你到达一切都被完全构造的点之前被执行。)在大括号之后的任何其他代码(比如int c = a + b;) 使编译器说“哦,好的,没有其他构造函数,然后我们可以初始化所有内容。” 所以它运行并初始化你的超类和你的成员等等,然后在大括号之后开始执行代码。

If, a few lines later, it meets some code saying "oh yeah when you're constructing this object, here are the parameters I want you to pass along to the constructor for the base class", it's too late and it doesn't make any sense. So you get a compiler error.

如果几行之后,它遇到一些代码说“哦,是的,当你构造这个对象时,这是我希望你传递给基类的构造函数的参数”,那就太晚了,它没有有任何意义。所以你会得到一个编译器错误。

回答by pendor

I've found a way around this by chaining constructors and static methods. What I wanted to do looked something like this:

我通过链接构造函数和静态方法找到了解决这个问题的方法。我想做的事情是这样的:

public class Foo extends Baz {
  private final Bar myBar;

  public Foo(String arg1, String arg2) {
    // ...
    // ... Some other stuff needed to construct a 'Bar'...
    // ...
    final Bar b = new Bar(arg1, arg2);
    super(b.baz()):
    myBar = b;
  }
}

So basically construct an object based on constructor parameters, store the object in a member, and also pass the result of a method on that object into super's constructor. Making the member final was also reasonably important as the nature of the class is that it's immutable. Note that as it happens, constructing Bar actually takes a few intermediate objects, so it's not reducible to a one-liner in my actual use case.

所以基本上根据构造函数参数构造一个对象,将对象存储在一个成员中,并将该对象上的方法的结果传递给 super 的构造函数。使成员成为 final 也相当重要,因为类的性质是它是不可变的。请注意,实际上,构造 Bar 实际上需要一些中间对象,因此在我的实际用例中不能将其简化为单行。

I ended up making it work something like this:

我最终让它像这样工作:

public class Foo extends Baz {
  private final Bar myBar;

  private static Bar makeBar(String arg1,  String arg2) {
    // My more complicated setup routine to actually make 'Bar' goes here...
    return new Bar(arg1, arg2);
  }

  public Foo(String arg1, String arg2) {
    this(makeBar(arg1, arg2));
  }

  private Foo(Bar bar) {
    super(bar.baz());
    myBar = bar;
  }
}

Legal code, and it accomplishes the task of executing multiple statements before calling the super constructor.

合法的代码,在调用超级构造函数之前完成多条语句的执行任务。

回答by Randa Sbeity

Simply because this is the inheritance philosophy. And according to the Java language specification, this is how the constructor's body is defined:

仅仅因为这是继承哲学。根据 Java 语言规范,构造函数的主体是这样定义的:

ConstructorBody: { ExplicitConstructorInvocationopt   BlockStatementsopt}

ConstructorBody: { ExplicitConstructorInvocation opt   BlockStatements opt}

The first statement of a constructor body may be either

构造函数体的第一条语句可以是

  • an explicit invocation of another constructor of the same class (by using the keyword "this"); or
  • an explicit invocation of the direct superclass (by using the keyword "super")
  • 显式调用同一类的另一个构造函数(通过使用关键字“this”);或者
  • 直接超类的显式调用(通过使用关键字“super”)

If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of its direct superclass that takes no arguments. And so on.. there will be a whole chain of constructors called all the way back to the constructor of Object; "All Classes in the Java platform are Descendants of Object". This thing is called "Constructor Chaining".

如果构造函数体不以显式构造函数调用开始,并且被声明的构造函数不是原始类 Object 的一部分,则构造函数体以超类构造函数调用“super();”隐式开始,即对它的直接超类不带参数。依此类推.. 会有一整条构造函数链一直调用到 Object 的构造函数;“Java 平台中的所有类都是对象的后代”。这个东西叫做“构造函数链”。

Now why is this?
And the reason why Java defined the ConstructorBody in this way, is that they needed to maintain the hierarchyof the object. Remember the definition of the inheritance; It's extending a class. With that being said, you cannot extend something that doesn't exist. The base (the superclass) needs to be created first, then you can derive it (the subclass). That's why they called them Parent and Child classes; you can't have a child without a parent.

这是为什么?
而Java之所以这样定义ConstructorBody,是因为他们需要维护对象的层次结构。记住继承的定义;它正在扩展一个类。话虽如此,你不能扩展不存在的东西。需要先创建基类(超类),然后才能派生它(子类)。这就是为什么他们称它们为父类和子类的原因;你不能没有父母的孩子。

On a technical level, a subclass inherits all the members (fields, methods, nested classes) from its parent. And since Constructors are NOT members (They don't belong to objects. They are responsible of creating objects) so they are NOT inherited by subclasses, but they can be invoked. And since at the time of object creation only ONE constructor is executed. So how do we guarantee the creation of the superclass when you create the subclass object? Thus the concept of "constructor chaining"; so we have the ability to invoke other constructors (i.e. super) from within the current constructor. And Java required this invocation to be the FIRST line in the subclass constructor to maintain the hierarchy and guarantee it. They assume that if you don't explicitly create the parent object FIRST (like if you forgot about it), they will do it implicitly for you.

在技​​术层面上,子类从其父类继承所有成员(字段、方法、嵌套类)。并且由于构造函数不是成员(它们不属于对象。它们负责创建对象)所以它们不被子类继承,但它们可以被调用。因为在创建对象时只执行一个构造函数. 那么我们在创建子类对象时如何保证超类的创建呢?于是就有了“构造函数链”的概念;所以我们有能力从当前构造函数中调用其他构造函数(即超级)。Java 要求这个调用是子类构造函数中的第一行,以维护层次结构并保证它。他们假设如果你没有明确地首先创建父对象(就像你忘记了它),他们会为你隐式地创建。

This check is done during compilation. But I'm not sure what would happen on runtime, what kind of runtime error we would get, IF Java doesn't throw a compile-error when we explicitly try to execute a base constructor from within a subclass's constructor in the middle of its body and not from the very first line ...

该检查是在编译期间完成的。但是我不确定运行时会发生什么,我们会得到什么样的运行时错误,如果当我们在子类的构造函数中间显式尝试执行基构造函数时,Java 不会抛出编译错误身体而不是从第一行开始......

回答by Dobes Vandermeer

My guess is they did this to make life easier for people writing tools that process Java code, and to some lesser degree also people who are reading Java code.

我的猜测是,他们这样做是为了让编写处理 Java 代码的工具的人们的生活更轻松,并且在某种程度上也是阅读 Java 代码的人们的生活。

If you allow the super()or this()call to move around, there are more variations to check for. For example if you move the super()or this()call into a conditional if()it might have to be smart enough to insert an implicit super()into the else. It might need to know how to report an error if you call super()twice, or use super()and this()together. It might need to disallow method calls on the receiver until super()or this()is called and figuring out when that is becomes complicated.

如果您允许super()this()调用移动,则需要检查更多变化。例如,如果您将super()orthis()调用移动到条件中,if()它可能必须足够聪明才能将隐式super()插入else. 它可能需要知道如果您调用super()两次或一起使用super()andthis()时如何报告错误。它可能需要在接收器上禁止方法调用,直到super()this()被调用并弄清楚何时变得复杂。

Making everyone do this extra work probably seemed like a greater cost than benefit.

让每个人都做这些额外的工作似乎成本大于收益。

回答by Tip-Sy

So, it is not stopping you from executing logic before the call to super. It is just stopping you from executing logic that you can't fit into a single expression.

因此,它不会阻止您在调用 super 之前执行逻辑。它只是阻止您执行无法放入单个表达式的逻辑。

Actually you can execute logic with several expessions, you just have to wrap your code in a static function and call it in the super statement.

实际上,您可以使用多个表达式执行逻辑,您只需要将代码包装在一个静态函数中并在 super 语句中调用它。

Using your example:

使用您的示例:

public class MySubClassC extends MyClass {
    public MySubClassC(Object item) {
        // Create a list that contains the item, and pass the list to super
        super(createList(item));  // OK
    }

    private static List createList(item) {
        List list = new ArrayList();
        list.add(item);
        return list;
    }
}