日々常々

ふつうのプログラマがあたりまえにしたいこと。

イニシャライザのどうでもいい話

すごくどうでもいいんだけど、Javaにはインスタンスイニシャライザとコンストラクタというのがあって。

class Hoge {
  // これがインスタンスイニシャライザ
  {
    int i = 0;
    i++;
    i++;
    i++;
  }

  // これがコンストラクタ
  Hoge() {
    long l = 0;
    l--;
    l--;
    l--;
  }
}

これを javap するとこうなって、インスタンスイニシャライザはみあたらない。

% javap -p Hoge
Compiled from "Hoge.java"
class Hoge {
  Hoge();
}

別に消えてるのではなくて、コンストラクタに展開されるん。 javap -c すると

% javap -c Hoge
Compiled from "Hoge.java"
class Hoge {
  Hoge();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: iconst_0
       5: istore_1
       6: iinc          1, 1
       9: iinc          1, 1
      12: iinc          1, 1
      15: lconst_0
      16: lstore_1
      17: lload_1
      18: lconst_1
      19: lsub
      20: lstore_1
      21: lload_1
      22: lconst_1
      23: lsub
      24: lstore_1
      25: lload_1
      26: lconst_1
      27: lsub
      28: lstore_1
      29: return
}

べろっと。 iinc ならんでるとこがインスタンスイニシャライザ由来で、 lsub とかがコンストラクタ由来なのは雰囲気感じられればOK。

これだけだとふーんって感じだけど、コンストラクタはオーバーロードできるんだよね。

class Hoge {
  // これがインスタンスイニシャライザ
  {
    int i = 0;
    i++;
    i++;
    i++;
  }

  // これがコンストラクタ
  Hoge() {
    long l = 0;
    l--;
    l--;
    l--;
  }

+ // オーバーロードしたコンストラクタ
+ Hoge(Object o) {
+   o.toString();
+  }
}

こうしてもどちらのコンストラクタが実行されるときもインスタンスイニシャライザは実行されるんだけど、コンストラクタに展開されるってことはー

% javap -c Hoge
Compiled from "Hoge.java"
class Hoge {
  Hoge();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: iconst_0
       5: istore_1
       6: iinc          1, 1
       9: iinc          1, 1
      12: iinc          1, 1
      15: lconst_0
      16: lstore_1
      17: lload_1
      18: lconst_1
      19: lsub
      20: lstore_1
      21: lload_1
      22: lconst_1
      23: lsub
      24: lstore_1
      25: lload_1
      26: lconst_1
      27: lsub
      28: lstore_1
      29: return

  Hoge(java.lang.Object);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: iconst_0
       5: istore_2
       6: iinc          2, 1
       9: iinc          2, 1
      12: iinc          2, 1
      15: aload_1
      16: invokevirtual #7                  // Method java/lang/Object.toString:()Ljava/lang/String;
      19: pop
      20: return
}

なるほど、イニシャライザが複製されておる。

で、コンストラクタはチェーンできるんだけど

class Hoge {
  // これがインスタンスイニシャライザ
  {
    int i = 0;
    i++;
    i++;
    i++;
  }

  // これがコンストラクタ
  Hoge() {
    long l = 0;
    l--;
    l--;
    l--;
  }

  // オーバーロードしたコンストラクタ
  Hoge(Object o) {
+   this();
    o.toString();
  }
}

これで展開されてるとイニシャライザが2回実行されることになるので、当然

% javap -c Hoge
Compiled from "Hoge.java"
class Hoge {
  Hoge();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: iconst_0
       5: istore_1
       6: iinc          1, 1
       9: iinc          1, 1
      12: iinc          1, 1
      15: lconst_0
      16: lstore_1
      17: lload_1
      18: lconst_1
      19: lsub
      20: lstore_1
      21: lload_1
      22: lconst_1
      23: lsub
      24: lstore_1
      25: lload_1
      26: lconst_1
      27: lsub
      28: lstore_1
      29: return

  Hoge(java.lang.Object);
    Code:
       0: aload_0
       1: invokespecial #7                  // Method "<init>":()V
       4: aload_1
       5: invokevirtual #10                 // Method java/lang/Object.toString:()Ljava/lang/String;
       8: pop

きえるわけだ。

これ自体は明示的にコンストラクタ呼び出しをすることで親クラスのコンストラクタ呼び出しとかも消えるし、特に違和感があるでもない。 けど、Javaコードが増えてバイトコードが減るのが面白いなぁって。

Hoge.java Hoge.class
this()なし 310 394
this()あり 322 372

インスタンスイニシャライザなんて書かないだろから通常のアプリケーション開発ではほんとどうでもいいことなんだろけど、任意のバイトコードをいじったりするツールとか作るなら気にしなきゃなのかなぁ。

私個人だと 昔から「コンストラクタはチェーンしよう」って言ってるし、イニシャライザを書くこともそんなにないからこの状況になることもそうないんだけども。

なお実行環境は以下の通り。あんま関係ないと思う。

% java -version
openjdk version "21.0.7" 2025-04-15 LTS
OpenJDK Runtime Environment Temurin-21.0.7+6 (build 21.0.7+6-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.7+6 (build 21.0.7+6-LTS, mixed mode, sharing)'

追記:バイトコード上での名前とかstaticイニシャライザとかの話

もともとこれ見る発端が、コンストラクタもインスタンスイニシャライザもコンパイルすると <init> って名前でクラスファイルに入ることから、両方あったらどうなるんだっけ?とみたものです。 javap で見るところでは「コンストラクタでしょ?」とJavaコードで書くコンストラクタのように示されていますが、たとえば // Method java/lang/Object."<init>":()V などで「親クラスのコンストラクタ」とかが出てきています。この <init> がコンストラクタおよびインスタンスイニシャライザです。 で、名前が同じもんだから、バイトコードだけで見るとコンストラクタとインスタンスイニシャライザの区別がつかなかったりするわけです。実害はない。

なおstaticイニシャライザでは <clinit> という別の名前が割り当てられるので、混ざったり展開されたりはしないようです。