为什么在紧凑构造器中调用记录访问器会返回默认字段值?
我在尝试使用Java的 record(记录类型)和紧凑构造器。
给定这个记录:
public record Point(double x, double y) {
public Point {
if (x > y) {
System.out.println(this.x());
}
}
public void printXAndY() {
System.out.printf("X: %.2f; Y: %.2f%n", this.x, this.y);
}
}
当我创建:
new Point(5.0, 3.0);
输出是:
0.0
然而,如果我直接读取字段:
System.out.println(this.x);
编译器报告一个错误,指出该字段无法被读取。
我的理解是:
- 在紧凑构造器中,字段是在构造器主体之后被赋值。
- 因此,在尚未初始化之前读取this.x是不合法的。
- 但调用this.x() 可以编译通过。
- 在运行时,它返回0.0,似乎是字段的默认值。
我的问题是,为什么调用访问器方法(this.x())会返回字段的默认值,而不是构造函数参数的值?
解决方案
这是Java的正常行为,对于“普通”类也同样适用。例如,下面作为普通类的等价实现也能编译通过。
public class Point {
private final int x;
private final int y;
public DummyClass(int x, int y) {
if (x > y) {
System.out.println(getX());
}
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
static void main() {
new Point(5, 4);
}
}
(注:Java 25 main 不再需要 public 也不需要 String[] 参数。)
从编译器的角度来看,进行的检查是“不完整的”。对于方法调用(在你的问题中是 this.x(),在我的示例中是 getX()),编译器仅检查该方法的返回类型是否可以传递给 System.out.println()(即不是 void)。它不会检查在这一点上该方法是否引用了尚未被明确赋值的变量。因此从编译器的角度来看,一切都没问题。
本质上,编译器只检查你在构造函数或初始化器的主体中,在被明确赋值(初始化)之前,是否引用了 this.x。在构造函数中调用的其他方法被视为一个黑箱。
而在运行时,x 在这一点确实有一个值,尽管它是final,且在构造函数中尚未被赋值:它是 0。因此,这也不会导致运行时错误或其他未定义行为(像在某些其他语言中那样)。
这也是为什么在非最终类中不建议在构造函数中调用非final(可覆盖)方法的主要原因。如果你在构造函数中调用了一个非最终方法,而子类覆盖了它,方法可能引用尚未初始化的字段(因为子类构造函数会在父类构造函数之后再进行赋值)。这会导致该方法看到/使用默认值,或者如果子类构造函数显式赋值字段,则会覆盖方法设置的值,导致更新丢失。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。