1. Overview
1.概述
In this short tutorial, we’ll show how we can get the error Cannot reference “X” before supertype constructor has been called, and how to avoid it.
在这个简短的教程中,我们将展示如何获得错误在调用超类型构造函数之前不能引用 “X”,以及如何避免它。
2. Constructors Chain
2.构造者链
A constructor can call exactly one other constructor. This call must be in the first line of its body.
一个构造函数可以精确地调用另一个构造函数。这个调用必须在其正文的第一行。
We can call a constructor of the same class with the keyword this, or we can call a constructor of the superclass with the keyword super.
我们可以用关键字this来调用同一个类的构造函数,或者用关键字super来调用超类的构造函数。
When a constructor doesn’t call another constructor, the compiler adds a call to the no-argument constructor of the superclass.
当一个构造函数没有调用另一个构造函数时,编译器会添加一个对超类的无参数构造函数的调用。
3. Our Compilation Error
3.我们的编译错误
This error boils down to trying to access instance level members before we invoke the constructor chain.
这个错误可以归结为在我们调用构造函数链之前试图访问实例级成员。
Let’s see a couple of ways we might run into this.
让我们看看我们可能遇到的几种情况。
3.1. Referring to an Instance Method
3.1.引用一个实例方法
In the next example, we’ll see the compilation error Cannot reference “X” before supertype constructor has been called at line 5. Note that constructor attempts to use the instance method getErrorCode() too early:
在下一个例子中,我们将在第5行看到编译错误在调用超类型构造函数之前不能引用 “X”。注意,构造函数试图过早地使用实例方法getErrorCode()。
public class MyException extends RuntimeException {
private int errorCode = 0;
public MyException(String message) {
super(message + getErrorCode()); // compilation error
}
public int getErrorCode() {
return errorCode;
}
}
This errors because, until super() has completed, there isn’t an instance of the class MyException. Therefore, we can’t yet make our call to the instance method getErrorCode().
这个错误是因为,u直到super()完成,还没有一个MyException类的实例。因此,我们还不能对实例方法getErrorCode()进行调用。
3.2. Referring to an Instance Field
3.2.引用一个实例字段
In the next example, we see our exception with an instance field instead of an instance method. Let’s take a look at how the first constructor tries to use an instance member before the instance itself is ready:
在下一个例子中,我们看到我们的异常有一个实例字段而不是一个实例方法。让我们看看第一个构造函数是如何在实例本身准备好之前试图使用一个实例成员的:。
public class MyClass {
private int myField1 = 10;
private int myField2;
public MyClass() {
this(myField1); // compilation error
}
public MyClass(int i) {
myField2 = i;
}
}
A reference to an instance field can only be made after its class has been initialized, meaning after any call to this() or super().
对一个实例字段的引用只能在其类被初始化后进行,也就是在对this()或super()的任何调用之后。
So, why is there no compiler error in the second constructor, which also uses an instance field?
那么,为什么在第二个构造函数中没有出现编译器错误,因为它也使用了一个实例字段?
Remember that all classes are implicitly derived from class Object, and so there is an implicit super() call added by the compiler:
请记住,所有的类都隐含地派生自Object类,因此有一个隐含的super() 调用被编译器添加。
public MyClass(int i) {
super(); // added by compiler
myField2 = i;
}
Here, Object‘s constructor gets called before we access myField2, meaning we’re okay.
在这里,Object的构造函数在我们访问myField2之前被调用,意味着我们没有问题。
4. Solutions
4.解决方案
The first possible solution to this problem is trivial: we don’t call the second constructor. We do explicitly in the first constructor what we wanted to do in the second constructor.
这个问题的第一个可能的解决方案是微不足道的。我们不调用第二个构造函数。我们在第一个构造函数中明确地做我们想在第二个构造函数中做的事情。
In this case, we’d copy the value of myField1 into myField2:
在这种情况下,我们会把myField1的值复制到myField2。
public class MyClass {
private int myField1 = 10;
private int myField2;
public MyClass() {
myField2 = myField1;
}
public MyClass(int i) {
myField2 = i;
}
}
In general, though, we probably need to rethink the structure of what we’re building.
不过,总的来说,我们可能需要重新思考我们正在建设的结构。。
But, if we’re calling the second constructor for a good reason, for example, to avoid repeating code, we can move the code into a method:
但是,如果我们调用第二个构造函数有充分的理由,例如,为了避免重复代码,我们可以将代码移到一个方法中:。
public class MyClass {
private int myField1 = 10;
private int myField2;
public MyClass() {
setupMyFields(myField1);
}
public MyClass(int i) {
setupMyFields(i);
}
private void setupMyFields(int i) {
myField2 = i;
}
}
Again, this works because the compiler has implicitly called the constructor chain before invoking the method.
同样,这也是由于编译器在调用方法之前隐含地调用了构造器链。
A third solution could be that we use static fields or methods. If we change myField1 to a static constant, then the compiler is also happy:
第三种解决方案是,我们使用静态字段或方法。如果我们把myField1改为静态常量,那么编译器也会很高兴。
public class MyClass {
private static final int SOME_CONSTANT = 10;
private int myField2;
public MyClass() {
this(SOME_CONSTANT);
}
public MyClass(int i) {
myField2 = i;
}
}
We should note that making a field static means that it becomes shared with all the instances of this object, so it’s not a change to make too lightly.
我们应该注意,让一个字段成为静态意味着它将与这个对象的所有实例共享,所以这不是一个可以轻易做出的改变。
For static to be the right answer, we need a strong reason. For example, maybe the value is not really a field, but instead a constant, so it makes sense to make it static and final. Maybe the construction method we wanted to call doesn’t need access to the instance members of the class, meaning it should be static.
要使static成为正确的答案,我们需要一个强有力的理由。例如,也许这个值并不是一个字段,而是一个常量,所以让它成为static和final是合理的。也许我们想调用的构造方法不需要访问类的实例成员,意味着它应该是static。
5. Conclusion
5.结论
We saw in this article how making a reference to instance members before the super() or this() call gives a compilation error. We saw this happen with an explicitly declared base class and also with the implicit Object base class.
我们在这篇文章中看到,在super()或this()调用之前对实例成员进行引用会产生一个编译错误。我们看到这种情况发生在显式声明的基类和隐式Object基类中。
We also demonstrated that this is an issue with the design of the constructor and showed how this can be fixed by repeating code in the constructor, delegating to a post-construction setup method, or the use of constant values or static methods to help with construction.
我们还证明了这是构造函数的设计问题,并展示了如何通过重复构造函数中的代码、委托给构造后的设置方法或使用常量值或静态方法来帮助构造来解决这个问题。
As always the source code for this example can be found over on GitHub.
像往常一样,这个例子的源代码可以在GitHub上找到over。