为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

qq_笑_17 2019-04-20 17:15:52
Java要求如果在构造函数中调用this()或super(),它必须是第一个语句。为什么?例如: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     }}Sun编译器说“调用super必须是构造函数中的第一个语句”。Eclipse编译器说“构造函数调用必须是构造函数中的第一个语句”。但是,您可以通过重新安排代码来解决这个问题:public class MySubClass extends MyClass {     public MySubClass(int a, int b) {         super(a + b);  // OK     }}这是另一个例子: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     }}因此,它不会阻止您在调用super之前执行逻辑。它只是阻止你执行不能适合单个表达式的逻辑。调用有类似的规则this()。编译器说“调用this必须是构造函数中的第一个语句”。为什么编译器有这些限制?你能给出一个代码示例吗,如果编译器没有这个限制,会发生什么不好的事情?
查看完整描述

17 回答

?
幕布斯6054654

TA贡献1876条经验 获得超7个赞

1)检查超级是第一个声明是不足以防止这个问题。例如,你可以把“super(someMethodInSuper());” 在你的构造函数中。这会尝试在构造超类之前访问它,即使super是第一个语句。(2)编译器似乎实现了一个不同的检查,它本身就足以防止这个问题。消息是“在调用超类型构造函数之前无法引用xxx”。因此,检查super是第一个语句是没有必要的。

查看完整回答
反对 回复 2019-05-15
?
浮云间

TA贡献1829条经验 获得超4个赞

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

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;
  }}

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

我最终让它的工作方式如下:

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;
  }}

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


查看完整回答
反对 回复 2019-05-15
?
弑天下

TA贡献1818条经验 获得超8个赞

因为JLS这么说。是否可以以兼容的方式更改JLS以允许它?对。但是,它会使语言规范变得复杂,这已经非常复杂了。它不是一个非常有用的东西,它有很多方法(用方法的结果调用另一个构造函数this(fn())- 该方法在另一个构造函数之前调用,因此也是超级构造函数)。因此,进行改变的功率重量比是不利的。

编辑2018年3月:在消息记录:建筑和验证甲骨文是在暗示这个限制被删除(但不像C#,this将是绝对未分配的构造函数链之前(DU))。

从历史上看,this()或super()必须是构造函数中的第一个。这种限制从未受到欢迎,并被认为是武断的。有许多微妙的原因,包括验证特殊参与,导致了这种限制。多年来,我们已经在虚拟机层面解决了这些问题,以至于考虑解除这一限制变得切实可行,不仅仅是记录,而是所有构造函数。


查看完整回答
反对 回复 2019-05-15
?
慕莱坞森

TA贡献1810条经验 获得超4个赞

我相当肯定(那些熟悉Java规范的人)会阻止你(a)被允许使用部分构造的对象,以及(b)强迫父类的构造函数构造为“新的” “对象。

一些“坏”的例子是:

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
    }        }


查看完整回答
反对 回复 2019-05-15
?
慕哥6287543

TA贡献1831条经验 获得超10个赞

你问为什么,以及其他答案,imo,并没有真正说出为什么可以调用你的超级构造函数,但只有它是第一行。原因是你并没有真正调用构造函数。在C ++中,等效语法是

MySubClass: MyClass {public:

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

当您在开放式大括号之前看到自己的初始化子句时,您知道它是特殊的。它在任何其余构造函数运行之前运行,实际上在任何成员变量初始化之前运行。Java并没有那么不同。在构造函数真正启动之前,有一种方法可以在初始化子类的任何成员之前运行一些代码(其他构造函数)。这种方式是将“调用”(例如super)放在第一行。(在某种程度上,在第一个开放式大括号之前,super或者this在第一个开放大括号之前,即使你在之后键入它,因为它将在你完成所有内容之前执行。)开放大括号之后的任何其他代码(如int c = a + b;)使编译器说“哦,好吧,没有其他构造函数,我们可以初始化所有内容。” 所以它会运行并初始化你的超类和你的成员以及诸如此类的东西,然后在开放式大括号之后开始执行代码。

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


查看完整回答
反对 回复 2019-05-15
?
尚方宝剑之说

TA贡献1788条经验 获得超4个赞

因此,它不会阻止您在调用super之前执行逻辑。它只是阻止你执行不能适合单个表达式的逻辑。

实际上你可以用几次尝试来执行逻辑,你只需要将你的代码包装在一个静态函数中并在super语句中调用它。

使用你的例子:

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;
    }}


查看完整回答
反对 回复 2019-05-15
?
一只名叫tom的猫

TA贡献1906条经验 获得超3个赞

我完全同意,限制太强了。使用静态辅助方法(如Tom Hawtin - 搭建建议)或将所有“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
    }}

正如Carson Myers建议的那样,使用“尚未构造的对象”例外会有所帮助,但在每个对象构造期间检查这个会减慢执行速度。我倾向于使Java编译器更好地区分(而不是随后禁止if语句但允许参数中的?-operator),即使这会使语言规范复杂化。


查看完整回答
反对 回复 2019-05-15
?
哈士奇WWW

TA贡献1799条经验 获得超6个赞

构造函数按推导顺序完成执行是有道理的。因为超类不知道任何子类,所以它需要执行的任何初始化都与子类执行的任何初始化分离,并且可能是先决条件。因此,它必须首先完成其执行。

一个简单的演示:

class A {
    A() {
        System.out.println("Inside A's constructor.");
    }}class B extends A {
    B() {
        System.out.println("Inside B's constructor.");
    }}class C extends B {
    C() {
        System.out.println("Inside C's constructor.");
    }}class CallingCons {
    public static void main(String args[]) {
        C c = new C();
    }}

该程序的输出是:

Inside A's constructorInside B's constructorInside C's constructor


查看完整回答
反对 回复 2019-05-15
?
侃侃尔雅

TA贡献1801条经验 获得超16个赞

在调用它的构造函数之前,您可以使用匿名初始化程序块初始化子项中的字段。这个例子将证明:

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");
    }}

这将输出:

在父级中 
初始化者
在子级中


查看完整回答
反对 回复 2019-05-15
?
泛舟湖上清波郎朗

TA贡献1818条经验 获得超3个赞

我找到了一个woraround。

这不会编译:

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

这有效:

public class MySubClass extends MyClass {
    public MySubClass(int a, int b) {
        this(a + b);
        doSomething2(a);
        doSomething3(b);
    }

    private MySubClass(int c) {
        super(c);
        doSomething(c);
    }}


查看完整回答
反对 回复 2019-05-15
?
千巷猫影

TA贡献1829条经验 获得超7个赞

那是因为你的构造函数依赖于其他构造函数。对于构造函数正常工作,其他构造函数必须正常工作,这是依赖的。这就是为什么必须首先检查依赖构造函数,这些构造函数首先在构造函数中由this()或super()调用。如果由this()或super()调用的其他构造函数有问题,那么什么点执行其他语句,因为如果被调用的构造函数失败,则所有构造函数都会失败。


查看完整回答
反对 回复 2019-05-15
?
千万里不及你

TA贡献1784条经验 获得超9个赞

实际上,super()是构造函数的第一个语句,因为要确保它的超类在构造子类之前是完全形成的。即使super()你的第一个语句中没有,编译器也会为你添加它!


查看完整回答
反对 回复 2019-05-15
  • 17 回答
  • 0 关注
  • 1317 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号