すごくどうでもいいんだけど、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
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
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
4: iconst_0
5: istore_2
6: iinc 2, 1
9: iinc 2, 1
12: iinc 2, 1
15: aload_1
16: invokevirtual
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
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
4: aload_1
5: invokevirtual
8: pop
きえるわけだ。
これ自体は明示的にコンストラクタ呼び出しをすることで親クラスのコンストラクタ呼び出しとかも消えるし、特に違和感があるでもない。
けど、Javaコードが増えてバイトコードが減るのが面白いなぁって。
インスタンスイニシャライザなんて書かないだろから通常のアプリケーション開発ではほんとどうでもいいことなんだろけど、任意のバイトコードをいじったりするツールとか作るなら気にしなきゃなのかなぁ。
私個人だと 昔から「コンストラクタはチェーンしよう」って言ってるし、イニシャライザを書くこともそんなにないからこの状況になることもそうないんだけども。
なお実行環境は以下の通り。あんま関係ないと思う。
% 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> という別の名前が割り当てられるので、混ざったり展開されたりはしないようです。