Records の Compact Constructors の中ではフィールドを初期化する処理は書けない

Java の Records に Compact Constructors と呼ばれる機能があります。

record Foo(int x) {
    Foo {
        x = 100;
    }
}

Compact Constructors の中ではフィールドを初期化する処理は書けません。コンパイルエラーになります。

record Foo(int x) {
    Foo {
        this.x = 100; // エラー: final変数xに値を代入することはできません
    }
}

ふつうのコンストラクタの場合は問題なし。

record Foo(int x) {
    Foo(int x) {
        this.x = 100; // OK
    }
}

なんであえてコンパイルエラーになるようにしたんだろうか...?

そのあたりの理由というか経緯は見つけられませんでしたが、とりあえず最初の Compact Constructors 版を javap してみました。

> java --version
openjdk 15 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)

> javac --enable-preview --release 15 Foo.java

> javap -c Foo.class
Compiled from "Foo.java"
final class Foo extends java.lang.Record {
  Foo(int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Record."<init>":()V
       4: bipush        100
       6: istore_1
       7: aload_0
       8: iload_1
       9: putfield      #7                  // Field x:I
      12: return

  public final java.lang.String toString();
    Code:
       0: aload_0
       1: invokedynamic #13,  0             // InvokeDynamic #0:toString:(LFoo;)Ljava/lang/String;
       6: areturn

  public final int hashCode();
    Code:
       0: aload_0
       1: invokedynamic #17,  0             // InvokeDynamic #0:hashCode:(LFoo;)I
       6: ireturn

  public final boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: invokedynamic #21,  0             // InvokeDynamic #0:equals:(LFoo;Ljava/lang/Object;)Z
       7: ireturn

  public int x();
    Code:
       0: aload_0
       1: getfield      #7                  // Field x:I
       4: ireturn
}

どうやらコンストラクタのローカル変数に代入して、そのあとにインスタンスフィールドを初期化してるっぽいです。おそらく、Compact Constructors に書いた処理はインスタンスフィールドの初期化の前に差し込まれるのではないかと思います。(想像で書いてるので正確ではないと思います)

最初の Compact Constructors 版の record は次のようなクラスになっていると思われます。

class Foo extends Record {
    private final int x;
    public Foo(int x) {
        x = 100;
        this.x = x;
    }
    // ... (略)
}

ちなみにふつうのコンストラクタ版を javap するとこういう感じ。

> javap -c Foo.class
Compiled from "Foo.java"
final class Foo extends java.lang.Record {
  Foo(int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Record."<init>":()V
       4: aload_0
       5: bipush        100
       7: putfield      #7                  // Field x:I
      10: return

  ... ()

参考

追記

ここにいろいろ書いてありました。(原文)

blogs.oracle.com

コンパクト・コンストラクタを宣言する目的は、カノニカル・コンストラクタの本体で必要となる、検証や正規化のみを行うコードを提供することにあります。その他の初期化コードはコンパイラが提供します。

あと、このあたりにもいろいろ書いてあるっぽい。ちゃんと読んでないですが...。

8.10.4 Record Constructor Declarations - Records - Oracle Help Center