【Java 22】 super(...) 之前添加语句
在学习 Java 的时候,我们都会记得一条 Java 的语法规则,与 Java 的类继承和构造器有关。在继承一个类的时候,子类的构造器必须要使用 super(...)
来调用父类的构造器。这个 super(...)
语句必须是子类构造器方法的第一条语句。如果 super(...)
不是第一条语句,就会出现编译错误。这一条规则对于调用同一个类的构造器也是适用的,也就是调用 this(...)
的时候。构造器中的 this(...)
也必须是第一条语句。
当一个类继承另外一个类时,子类从父类继承了功能,并添加了自己的字段和方法。子类的字段的初始值可能依赖于父类中字段的初始值。因此,父类的字段必须要先于子类的字段完成初始化。
为了确保字段初始化的顺序,构造器必须按照自顶向下的方式来运行。父类的构造器的运行必须先于子类的构造器的运行。另外一个限制是,类的字段在初始化之前,不能被访问。为了确保这一点,构造器中的代码不能访问当前类中的字段,访问父类中的字段也必须等待父类的构造器运行完成。
为了确保父类的字段首先被初始化,Java 从 1.0 开始,就采用了一种非常严格的语法,那就是要求 super(...)
和 this(...)
必须是构造器的第一条语句。在这种限制下,肯定不会出现访问错误。但是也限制了很多合法的使用场景。
这个限制在 Java 22 被移除了。在构造器方法中,super(...)
和 this(...)
不再要求必须是第一条语句。在它们之前,可以有别的语句。当然,构造器自顶向下运行的要求并没有变化,字段在完成初始化之前也不能被访问。这两个限制仍然存在。在这个前提下,如果 super(...)
和 this(...)
之前有别的语句,编译器会进行更加严格的检查,确保这些语句不会破坏这两个限制。对于 Java 开发者来说,并不需要过多关注语法规则。如果有问题,编译器自然会报错,然后根据编译器的错误来修改即可。
在 Java 22 中,该功能目前还是预览状态,使用时需要添加
--enable-preview
。
允许在 super(...)
之前添加语句之后,在构造器的实现中,有三类与构造器参数相关的场景可以被简化。
第一类是对构造器的参数进行校验。如果子类的构造器的参数未通过校验,可以直接抛出异常,不需要调用父类的构造器。
在下面的代码中,子类 Child1
的构造器中,对参数 value
的值进行了校验。如果参数不合法,直接抛出异常,并不会调用 super(value)
。
class Super1 {
private int value;
Super1(int value) {
this.value = value;
}
}
public class Child1 extends Super1 {
Child1(int value) {
if (value < 0) {
throw new IllegalArgumentException("invalid value");
}
super(value);
}
}
上面的代码可以使用下面的命令来编译。命令行选项 --enable-preview
和 -source 22
都是必须的。
javac --enable-preview -source 22 Child1.java
如果使用 Java 22 之前的 Java 版本来进行编译,会出现下面的错误,表示对 super(...)
的调用必须是第一条语句。
Child1.java:14: error: call to super must be first statement in constructor
super(value);
^
1 error
第二类是对构造器的参数进行准备。这通常是因为父类构造器的参数,与子类构造器的参数,存在很大差别。子类构造器的方法中,必须执行一段比较复杂的逻辑,才能得到传递给父类构造器的参数。准备父类构造器的参数的这段逻辑,可以放在 super(...)
调用之前,使得构造器的代码更加清晰易懂。
在下面的代码中,子类构造器方法中有一段逻辑来计算出 newId
的值,来作为父类构造器的参数值。
import java.util.UUID;
class Super2 {
private String id;
Super2(String id) {
this.id = id;
}
}
public class Child2 extends Super2 {
Child2(String oldId) {
String newId;
if (oldId != null) {
newId = "o_" + oldId.toLowerCase();
} else {
newId = UUID.randomUUID().toString();
}
super(newId);
}
}
第三类是共享构造器的参数。父类的构造器可能有多个同样类型的参数,子类构造器用同一个值,作为父类构造器的多个参数的值。
在下面的代码中,父类构造器有两个 int
类型的参数,子类构造器只有一个 int
类型的参数。在调用 super(...)
时,复用了同一个参数值。下面的代码也展示了 this(...)
的用法,在 this(...)
之前也可以有语句。
class Super3 {
private int v1;
private int v2;
Super3(int v1, int v2) {
this.v1 = v1;
this.v2 = v2;
}
}
public class Child3 extends Super3 {
Child3(int v) {
int pv = v * 1024;
super(pv, pv);
}
Child3(String str) {
int v = str.length() * 100;
this(v);
}
}
Java 22 的这个改动,允许在 super(...)
和 this(...)
之前添加语句,可以在很多情况下简化构造器方法的代码。