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

Java为什么这样做的问题已经有了答案,但由于我无意中发现了这个问题,希望找到一个更好的单行程序的替代品,因此在此分享我的解决方法:

public class SomethingComplicated extends SomethingComplicatedParent {

    private interface Lambda<T> {
        public T run();
    }

    public SomethingComplicated(Settings settings) {
        super(((Lambda<Settings>) () -> {

            // My modification code,
            settings.setting1 = settings.setting2;
            return settings;
        }).run());
    }
}

调用静态函数应该执行得更好,但如果我坚持将代码“置于”构造函数内部,或者如果我必须更改多个参数,并且发现定义许多静态方法不利于可读性,我会使用这种方法。

在子类构造函数中添加super()的主要目标是编译器的主要工作是将所有类与Object类建立直接或间接的连接,这就是为什么编译器检查我们是否提供了super(参数化),然后编译器不承担任何责任。 这样所有的实例成员从Object初始化为子类。

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

如果允许super()或this()调用移动,则需要检查更多的变化。例如,如果您将super()或this()调用移动到条件if()中,它可能必须足够聪明,将隐式super()插入到else中。如果调用super()两次,或者同时使用super()和this(),它可能需要知道如何报告错误。它可能需要禁止在接收器上调用方法,直到super()或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().