【Java 22】 super(...) 之前添加语句

在学习 Java 的时候,我们都会记得一条 Java 的语法规则,与 Java 的类继承和构造器有关。在继承一个类的时候,子类的构造器必须要使用 super(...) 来调用父类的构造器。这个 super(...) 语句必须是子类构造器方法的第一条语句。如果 super(...) 不是第一条语句,就会出现编译错误。这一条规则对于调用同一个类的构造器也是适用的,也就是调用 this(...) 的时候。构造器中的 this(...) 也必须是第一条语句。

当一个类继承另外一个类时,子类从父类继承了功能,并添加了自己的字段和方法。子类的字段的初始值可能依赖于父类中字段的初始值。因此,父类的字段必须要先于子类的字段完成初始化。

为了确保字段初始化的顺序,构造器必须按照自顶向下的方式来运行。父类的构造器的运行必须先于子类的构造器的运行。另外一个限制是,类的字段在初始化之前,不能被访问。为了确保这一点,构造器中的代码不能访问当前类中的字段,访问父类中的字段也必须等待父类的构造器运行完成。

为了确保父类的字段首先被初始化,Java 从 1.0 开始,就采用了一种非常严格的语法,那就是要求 super(...)this(...) 必须是构造器的第一条语句。在这种限制下,肯定不会出现访问错误。但是也限制了很多合法的使用场景。

这个限制在 Java 22 被移除了。在构造器方法中,super(...)this(...) 不再要求必须是第一条语句。在它们之前,可以有别的语句。当然,构造器自顶向下运行的要求并没有变化,字段在完成初始化之前也不能被访问。这两个限制仍然存在。在这个前提下,如果 super(...)this(...) 之前有别的语句,编译器会进行更加严格的检查,确保这些语句不会破坏这两个限制。对于 Java 开发者来说,并不需要过多关注语法规则。如果有问题,编译器自然会报错,然后根据编译器的错误来修改即可。

JEP

在 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(...) 之前添加语句,可以在很多情况下简化构造器方法的代码。

版权所有 © 2024 灵动代码