Collections.emptyMapとCollections.EMPTY_MAPの違いを理解してなくて同僚にこっぴどく怒られたので自戒のためにここに書き記す
こんばんは、C1C2コンパイラの仕組みを知ってからJVM大好きっ子になったJava・Scala開発者です。(自分の呼称が安定しない)
今日(もう昨日か)、同僚に指摘されて、初めてCollectionsの空Mapの違いに関してきちんと認識したので、次回の意味も込めてここに投稿しておこうと思います。
Javaは初めて書いた時からかなり経つのですが、非常に初歩的でありながら全く知らなかったので、いい勉強になりました。
まず結論から
本題とそのあとの解説はグダグダ長いので、先に結論から書く。
Collections.emptyMap:
新しい空のMapインスタンスを作成するため、変数や返り値の型にrow typeを使っていない限りは型推論され型安全である。
Collections.EMPTY_MAP:
row typeなcanonicalオブジェクトのためジェネリクスに型指定がなく型安全ではない。
オラクルの公式JavaDocにもきちんと記載されている。
https://docs.oracle.com/javase/6/docs/api/java/util/Collections.html#emptyMap%28%29
Unlike this method, the field does not provide type safety. (このメソッドと違い、フィールド(のEMPTY_MAP)は型安全ではありません)
グダグダと長い本題と怒られた経緯
昼食を終えデスクでオライリのマイクロサービスアーキテクチャをパラパラと流し読みしていると、日本で働く外国人の同僚(仮にライナスと呼ぶことにする)から、いささか不穏なSlackメッセージが届いた。
ライナス:プロフィールにon Vacationって書いてあるけど、まだ休暇中?
僕:こころは無限に休暇中…(嘘です戻りました)どうした?
ラ:いや、君のコードがあまりにひどくて泣きたくなっってきてね…
僕:言うねえ…
ラ:自分で見てゴミだと思わないかい?
僕:Repositoryとしては問題ないと思うんだけど…
ラ:Repositoryねえ…
中々に剣呑である。(彼の名誉のために言っておくが、彼は技術面でのみ、特にくだらないミスや汚いコードに対してのみこのような言葉遣いになるが、普段はこの上なく紳士的でユーモアのあるナイスオタクガイである)
言い訳をすれば、前期の追い込みの時に書いたクラスだったのでいくつか問題点があるのは認識していたが、そこまでひどい設計だとは思えなかった。
彼はバックエンド班のリーダでなかなか多忙な人物なので、空いた時間でフィードバックをくれるようにお願いしてその場は別れた。
夕方、ある程度作業が落ち着いてきた頃、彼からまたメッセージが届いた。
ライナス:そのクラスの問題点にはそろそろ気づけたかい?
僕は思いつく限り設計の問題点をあげ、彼の反応を待った。
しかし、彼から帰ってきたのはもっと単純な答えだった。
いや、きみね、そういう大層なことを言う前にまず基本をやりなさいよ。
1.ImmutableMap.Builder retvalBuilder = ImmutableMap.<String, T>builder();
これraw typeじゃん
2.return Collections.EMPTY_MAP;
とreturn Collections.emptyMap();
の違い知ってる?
3.return this.getXXXByIds(Arrays.asList(id)).getOrDefault(id, null);
JavaDocでNPEに関してメンションしてる割に、nullをきちんと処理していないじゃん。なんでOptional返さないの? そもそもgetOrDefault(x, null)
とget(x)
は同じじゃん
1と3に関しては見た瞬間、赤面するほど恥ずかしかった。穴があったら入りたい。素直にごめんなさいするしかない。反省の意味も込めてここに晒しちゃう。
なにせ、こう言う基本的なことは僕自身がまた別の国で働く後輩たちに対して、レビュで繰り返し教えていることだったからだ。
しかし、2に関しては全く問題だとは思っていなかった。と、言うのも、Collections.EMPTY_MAPはcanonicalかつImmutableだ。どうせ変更される事がないならcanonicalオブジェクトを使った方が(誤差ほどではあるが)メモリにも優しいし良いかなくらいに思っていた。
しかし、いざJavaDocを読むと、その意味がはっきりした。
きちんと書いてあるのだ、返り値にEMPTY_MAPを使う危険性が。
Unlike this method, the field does not provide type safety. (このメソッドと違い、フィールド(のEMPTY_MAP)は型安全ではありません)
そう、型安全ではないのだ、EMPTY_MAPは。
僕たちJavaプログラマは Javaのもつ強力な型づけ機能に守られて生きている。
非型安全なコードを書くプログラマは、両親に守られて生きていながら両親を軽んじる、反抗期の中学生と大差ないこの上なく未熟な存在なのだ。
(言い過ぎましたごめんなさい)
てか、そもそもなんでSingltonなわけでもないのに定数とメソッド二つ用意されてるの?
Collections.EMPTY_MAPは、今の基準からすればそもそも必要のない定数なはずだが、リリースされたVersionを見てその理由がわかった。
Collections.EMPTY_MAPはJava3でリリースされ、Collections.emptyMapがリリースされたのはJava5なのだ。
今でこそ昔からいましたよと言う顔で鎮座するジェネリクスだが、この機能がJavaに入ったのはJava5、結構最近である。(僕がJavaを始めた頃は型の指定が出来なかったのでいちいちcastしていた。)
つまり、Collections.EMPTY_MAPはジェネリクスがない時代に作成されたため、型の指定を持たないcanonicalオブジェクトとして作成されたが、Java5になってジェネリクスが解禁されたため、より型安全なCollections.emptyMapメソッドが作成されたと言うことだろう。
当然標準ライブラリは後方互換性を保たなくてはいけないため、Collections.EMPTY_MAPは今も元気に野を走り回っているわけである。
……でも、正直これ使い道あるのかな。Deprecatedに指定されてないってことは多分想定された用途があるんだろうけど、個人的に今のJavaで正しく使う使い方がわからない。どなたかご存知だったらコメントで教えてください。
まとめ
Collectionsクラスの定数であるEMPTY_MAPは非型安全で、emptyMapメソッドは型安全。なので、基本的にJava5以上はemptyMapメソッドで空のMapを作った方が安全ということですね。
小さなことではあるのですが、僕もライナスもJavaやJVM(そしてあらゆる言語や基盤、OS開発者)に対してこの上なく敬意を払っているので、こう言う小さなミスも十分怒られてしかるべきことだと認識しています。
Javaを書き始めて結構たちますが、まだまだ知らないことがたくさんあって、本当に勉強のしがいのある言語だなぁと感じますね。
ちなみに冒頭で触れたマイクロサービスアーキテクチャに関しては、別の記事をブログとQiitaでシコシコ書き溜めてるので、後日公開します。
本題に全然関係ないまとめになっちゃったな、まぁいっか。では!