読者です 読者をやめる 読者になる 読者になる

C Sharpens you up

http://qiita.com/yuba に移しつつあります

JavaでもC#みたいなオブジェクト初期化子書けるんですよ

Java

コンストラクタが特に何もしてくれず、インスタンス作ってから初期化しないといけないタイプのクラスってありますよね。シリアライズのためだったりJavaBeansだったり、あとはふつーのArrayListだから作ってから内容詰め込みます、みたいに。

オブジェクト作ってreturnしたいだけなのに、作ったあとに初期化しないといけないから一時変数作るのがいつも一手余分で、一行で書けるラムダ式がブロックラムダになったりするといらいらもたまるというものです。

C#にはオブジェクト初期化子があるのでその余分な一手が省けますね。

return new Person { Name = "Alice", Age = 20 };

return new List<int> { 1, 1, 2, 3, 5, 8, };

return new Dictionary<int, String> {
    { 200, "OK" },
    { 301, "Moved Permanently" },
    { 404, "Not Found" },
    { 418, "I'm a teapot" },
};

Javaにオブジェクト初期化子はないのですが、実は似たような書き方が通用します。

return new Person() {{ name = "Alice"; age = 20; }};

return new ArrayList<Integer>() {{ add(1); add(1); add(2); add(3); add(5); add(8); }};

return new HashMap<Integer, String>() {{
    put(200, "OK");
    put(301, "Moved Permanently");
    put(404, "Not Found");
    put(418, "I'm a teapot");
}};

どうしてこの書き方ができるかの解説。

匿名クラス

コンストラクタ呼び出し直後に{ }ブロックを置くと匿名クラスの宣言・初期化になります。例文ではそれぞれPerson, ArrayList, HashMapのコンストラクタを呼んでいるわけではなく、Person, ArrayList, HashMapをそれぞれ継承した匿名クラスを作って、そのコンストラクタを呼んでいることになります。

初期化ブロック

クラス定義の中でメソッド名もアクセス制御子も何もつけずただ{ }ブロックを置くと、それは初期化ブロックになります。これはインスタンスが作成されたときにコンストラクタよりも早く呼ばれるブロックです。

匿名クラス、初期化ブロックを合わせると

return new Person() {{ name = "Alice"; age = 20; }};

という文はこう読めます。
「Personを拡張したクラスを作り、そのクラスは作成時にname = "Alice"; age = 20;を実行する。そのクラスのインスタンスを作成」

C#ととても似た目で同じような記述ができるのですが内部的な意味はまったく異なるわけです。一時変数を一個追い出すために匿名クラスが一本できるわけで、これは省資源的にいかがなものかと言われればその通りですが、ラムダ式が一行にできるかできないかってときは正当化されそうです。

あと、厳密にPersonインスタンスを要求しているところに匿名拡張Personを渡したりすると当然怒られるので相手は選んでください。

あわせてどうぞ【一時変数の駆除】