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必须是构造函数中的第一条语句。

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


当前回答

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

class Good {
    int essential1;
    int essential2;

    Good(int n) {
        if (n > 100)
            throw new IllegalArgumentException("n is too large!");
        essential1 = 1 / n;
        essential2 = n + 2;
    }
}

class Bad extends Good {
    Bad(int n) {
        try {
            super(n);
        } catch (Exception e) {
            // Exception is ignored
        }
    }

    public static void main(String[] args) {
        Bad b = new Bad(0);
//        b = new Bad(101);
        System.out.println(b.essential1 + b.essential2);
    }
}

An exception during construction almost always indicates that the object being constructed could not be properly initialized, now is in a bad state, unusable, and must be garbage collected. However, a constructor of a subclass has got the ability to ignore an exception occurred in one of its superclasses and to return a partially initialized object. In the above example, if the argument given to new Bad() is either 0 or greater than 100, then neither essential1 nor essential2 are properly initialized.

你可能会说忽略异常总是一个坏主意。好的,这里还有一个例子:

class Bad extends Good {
    Bad(int n) {
        for (int i = 0; i < n; i++)
            super(i);
    }
}

很有趣,不是吗?在这个例子中我们创建了多少个对象?一个?两个?或者什么都没有……

允许在构造函数中间调用super()或this()将打开一个令人讨厌的构造函数的潘多拉盒子。


另一方面,我理解在调用super()或this()之前经常需要包含一些静态部分。这可能是任何不依赖于此引用的代码(实际上,它已经存在于构造函数的最开始,但在super()或This()返回之前不能有序使用),并且需要进行这样的调用。此外,像在任何方法中一样,在调用super()或this()之前创建的一些局部变量可能会在调用super()或this()之后被需要。

在这种情况下,你有以下机会:

Use the pattern presented at this answer, which allows to circumvent the restriction. Wait for the Java team to allow pre-super() and pre-this() code. It may be done by imposing a restriction on where super() or this() may occur in a constructor. Actually, even today's compiler is able to distinguish good and bad (or potentially bad) cases with the degree enough to securely allow static code addition at the beginning of a constructor. Indeed, assume that super() and this() return this reference and, in turn, your constructor has

return this;

最后。以及编译器拒绝代码

public int get() {
    int x;
    for (int i = 0; i < 10; i++)
        x = i;
    return x;
}

public int get(int y) {
    int x;
    if (y > 0)
        x = y;
    return x;
}

public int get(boolean b) {
    int x;
    try {
        x = 1;
    } catch (Exception e) {
    }
    return x;
}

with the error "variable x might not have been initialized", it could do so on this variable, making its checks on it just like on any other local variable. The only difference is this cannot be assigned by any means other than super() or this() call (and, as usual, if there is no such call at a constructor, super() is implicitly inserted by compiler in the beginning) and might not be assigned twice. In case of any doubt (like in the first get(), where x is actually always assigned), the compiler could return an error. That would be better than simply return error on any constructor where there is something except a comment before super() or 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;
  }
}

基本上是根据构造函数的形参构造一个对象,将对象存储在成员中,并将该对象的方法的结果传递到super的构造函数中。使成员为final也是相当重要的,因为类的性质是不可变的。注意,构造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;
  }
}

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

因此,它不会阻止您在调用之前执行逻辑 超级。它只是阻止你执行你无法适应的逻辑 变成一个表达式。

实际上,你可以用几个表达式执行逻辑,你只需要把你的代码包装在一个静态函数中,然后在超级语句中调用它。

举个例子:

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

这是官方回放: 从历史上看,this()或super()在构造函数中必须位于第一个。这 限制从来不受欢迎,被认为是武断的。有一个 一些微妙的原因,包括验证调用特殊, 这导致了这种限制。这些年来,我们已经解决了 这些都是虚拟机级别的,直到它变得实用 考虑取消这一限制,不只是对记录,而是对所有人 构造函数。

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

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

让每个人都做额外的工作似乎是弊大于利。

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