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

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


当前回答

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:

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

如果编译器没有强制执行,你可以这样做:

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

在父类有默认构造函数的情况下,编译器会自动插入对super的调用。由于Java中的每个类都继承自Object,所以必须以某种方式调用Object的构造函数,并且必须首先执行它。编译器可以自动插入super()。强制super首先出现,强制构造函数主体以正确的顺序执行,即:Object -> Parent -> Child -> ChildOfChild -> SoOnSoForth

在构造子对象之前,必须先创建父对象。 如你所知,当你这样写类时:

public MyClass {
        public MyClass(String someArg) {
                System.out.println(someArg);
        }
}

它转向下一个(extend和super只是被隐藏了):

public MyClass extends Object{
        public MyClass(String someArg) {
                super();
                System.out.println(someArg);
        }
}

First we create an Object and then extend this object to MyClass. We can not create MyClass before the Object. The simple rule is that parent's constructor has to be called before child constructor. But we know that classes can have more that one constructor. Java allow us to choose a constructor which will be called (either it will be super() or super(yourArgs...)). So, when you write super(yourArgs...) you redefine constructor which will be called to create a parent object. You can't execute other methods before super() because the object doesn't exist yet (but after super() an object will be created and you will be able to do anything you want).

So why then we cannot execute this() after any method? As you know this() is the constructor of the current class. Also we can have different number of constructors in our class and call them like this() or this(yourArgs...). As I said every constructor has hidden method super(). When we write our custom super(yourArgs...) we remove super() with super(yourArgs...). Also when we define this() or this(yourArgs...) we also remove our super() in current constructor because if super() were with this() in the same method, it would create more then one parent object. That is why the same rules imposed for this() method. It just retransmits parent object creation to another child constructor and that constructor calls super() constructor for parent creation. So, the code will be like this in fact:

public MyClass extends Object{
        public MyClass(int a) {
                super();
                System.out.println(a);
        }
        public MyClass(int a, int b) {
                this(a);
                System.out.println(b);
        }
}

正如其他人所说,你可以这样执行代码:

this(a+b);

你也可以像这样执行代码:

public MyClass(int a, SomeObject someObject) {
    this(someObject.add(a+5));
}

但是你不能像这样执行代码,因为你的方法还不存在:

public MyClass extends Object{
    public MyClass(int a) {

    }
    public MyClass(int a, int b) {
        this(add(a, b));
    }
    public int add(int a, int b){
        return a+b;
    }
}

此外,在this()方法链中必须有super()构造函数。你不能像这样创建一个对象:

public MyClass{
        public MyClass(int a) {
                this(a, 5);
        }
        public MyClass(int a, int b) {
                this(a);
        }
}

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

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

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

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

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().

Tldr:

其他的答案都解决了这个问题的“为什么”。我将提供一个关于这个限制的hack:

基本思想是用嵌入式语句劫持超级语句。这可以通过将语句伪装成表达式来实现。

Tsdr:

假设我们想在调用super()之前执行Statement1()到Statement9():

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        super(_1, _2, _3); // compiler rejects because this is not the first line
    }
}

编译器当然会拒绝我们的代码。所以,我们可以这样做:

// This compiles fine:

public class Child extends Parent {
    public Child(T1 _1, T2 _2, T3 _3) {
        super(F(_1), _2, _3);
    }

    public static T1 F(T1 _1) {
        Statement_1();
        Statement_2();
        Statement_3(); // and etc...
        Statement_9();
        return _1;
    }
}

唯一的限制是父类必须有一个构造函数,该构造函数必须至少接受一个参数,以便我们可以将语句作为表达式潜入。

这里有一个更详细的例子:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        i = i * 10 - 123;
        if (s.length() > i) {
            s = "This is substr s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        Object obj = Static_Class.A_Static_Method(i, s, t1);
        super(obj, i, "some argument", s, t1, t2); // compiler rejects because this is not the first line
    }
}

改写成:

// This compiles fine:

public class Child extends Parent {
    public Child(int i, String s, T1 t1) {
        super(Arg1(i, s, t1), Arg2(i), "some argument", Arg4(i, s), t1, Arg6(i, t1));
    }

    private static Object Arg1(int i, String s, T1 t1) {
        i = Arg2(i);
        s = Arg4(s);
        return Static_Class.A_Static_Method(i, s, t1);
    }

    private static int Arg2(int i) {
        i = i * 10 - 123;
        return i;
    }

    private static String Arg4(int i, String s) {
        i = Arg2(i);
        if (s.length() > i) {
            s = "This is sub s: " + s.substring(0, 5);
        } else {
            s = "Asdfg";
        }
        return s;
    }

    private static T2 Arg6(int i, T1 t1) {
        i = Arg2(i);
        t1.Set(i);
        T2 t2 = t1.Get();
        t2.F();
        return t2;
    }
}

事实上,编译器可以为我们自动化这个过程。他们只是选择不这么做。