すごくどうでもいいんだけど、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イニシャライザとかの話
staticイニシャライザだとどうなるんだろ? https://t.co/UBmZrxk7HL
— Yasuo Nakanishi (@nakanishiyasuo) 2025年7月15日
もともとこれ見る発端が、コンストラクタもインスタンスイニシャライザもコンパイルすると <init>
って名前でクラスファイルに入ることから、両方あったらどうなるんだっけ?とみたものです。
javap
で見るところでは「コンストラクタでしょ?」とJavaコードで書くコンストラクタのように示されていますが、たとえば // Method java/lang/Object."<init>":()V
などで「親クラスのコンストラクタ」とかが出てきています。この <init>
がコンストラクタおよびインスタンスイニシャライザです。
で、名前が同じもんだから、バイトコードだけで見るとコンストラクタとインスタンスイニシャライザの区別がつかなかったりするわけです。実害はない。
なおstaticイニシャライザでは <clinit>
という別の名前が割り当てられるので、混ざったり展開されたりはしないようです。