JEP 447:super(...) 之前的语句(预览)
概括
在 Java 编程语言的构造函数中,允许不引用正在创建的实例的语句出现在显式构造函数调用之前。这是预览语言功能。
目标
-
为开发人员提供了更大的自由来表达构造函数的行为,从而可以更自然地放置目前必须纳入辅助静态方法、辅助中间构造函数或构造函数参数中的逻辑。
-
保留构造函数在类实例化期间按自上而下顺序运行的现有保证,确保子类构造函数中的代码不会干扰超类实例化。
-
不需要对 Java 虚拟机进行任何更改。此 Java 语言功能仅依赖于 JVM 验证和执行构造函数中显式构造函数调用之前出现的代码的当前能力。
动机
当一个类扩展另一个类时,子类从超类继承功能,并且可以通过声明自己的字段和方法来添加功能。子类中声明的字段的初始值可以取决于超类中声明的字段的初始值,因此首先初始化超类的字段,然后再初始化子类的字段是至关重要的。例如,如果 classB
扩展 class A
,则必须首先初始化未见过的类的字段Object
,然后是 class 的字段A
,然后是 class 的字段B
。
按此顺序初始化字段意味着构造函数必须从上到下运行:超类中的构造函数必须在子类中的构造函数运行之前完成对该类中声明的字段的初始化。这就是对象的整体状态的初始化方式。
确保类的字段在初始化之前不会被访问也很重要。防止访问未初始化的字段意味着必须限制构造函数:在超类中的构造函数完成之前,构造函数的主体不得访问在其自己的类或任何超类中声明的字段。
为了保证构造函数从上到下运行,Java 语言要求在构造函数主体中,对另一个构造函数的任何显式调用都必须出现在第一条语句中;如果没有给出显式构造函数调用,则由编译器注入。
为了保证构造函数不会访问未初始化的字段,Java 语言要求如果给出显式构造函数调用,则其任何参数都不能this
以任何方式访问当前对象 。
这些要求保证了自上而下的行为和初始化前禁止访问,但它们很严厉,因为它们使得普通方法中使用的一些习惯用法很难甚至不可能在构造函数中使用。下面的例子说明了这些问题。
示例:验证超类构造函数参数
有时我们需要验证传递给超类构造函数的参数。我们可以在事后验证论点,但这意味着可能会做不必要的工作:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(value); // Potentially unnecessary work
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
}
}
最好通过在调用超类构造函数之前验证其参数来声明一个快速失败的构造函数。今天我们只能使用辅助方法在线执行此操作static
:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
super(verifyPositive(value));
}
private static long verifyPositive(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
return value;
}
}
如果我们可以将验证逻辑直接包含在构造函数中,则此代码将更具可读性。我们想写的是:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0)
throw new IllegalArgumentException("non-positive value");
super(value);
}
}
示例:准备超类构造函数参数
有时我们必须执行重要的计算,以便为超类构造函数准备参数,再次求助于辅助方法:
public class Sub extends Super {
public Sub(Certificate certificate) {
super(prepareByteArray(certificate));
}
// Auxiliary method
private static byte[] prepareByteArray(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null)
throw new IllegalArgumentException("null certificate");
return switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
...
default -> ...
};
}
}
超类构造函数接受一个byte
数组参数,但子类构造函数接受一个Certificate
参数。为了满足超类构造函数调用必须是子类构造函数中的第一个语句的限制,我们声明辅助方法prepareByteArray
来为该调用准备参数。
如果我们可以将参数准备代码直接嵌入构造函数中,则该代码将更具可读性。我们想写的是:
public Sub(Certificate certificate) {
var publicKey = certificate.getPublicKey();
if (publicKey == null)
throw new IllegalArgumentException("null certificate");
final byte[] byteArray = switch (publicKey) {
case RSAKey rsaKey -> ...
case DSAPublicKey dsaKey -> ...
...
default -> ...
};
super(byteArray);
}