Google Guava の transform, filter 系のお話
Java 派な皆様は本日も Google Guava の恩恵を受けていることかと思われます。
ある時自社プロダクトのパフォーマンスを調べていて、プロファイルを取ったところLists.transform が上位に出てきてました。
調べてみると、Lists.transform は遅延評価で、リストの要素を触る度に transform 処理が行われることがわかりました。
たとえば、下記コード。
List<Integer> values = ... List<String> strings = Lists.transform(values, new Function<Integer, String>() { @Override public String apply(Integer i) { return doSomething(i); } });
これは values の各要素に対して第二引数の function を適用したリストを返すコードに見えますが、
Lists.transform を呼んだ時点では function は適用されないのです。
一見、下記コードとほぼ同等であることを期待しますが、全く違います。
List<Integer> values = ... List<String> strings = Lists.newArrayList(); for(Integer i : values){ strings.add(doSomething(i)); }
ではいつ各要素に、transform の第二引数の function が適用されるのでしょう?リストの要素を触った時です。
下記コードがわかりやすいでしょう。
List<Integer> values = Lists.newArrayList(1, 2, 3); List<String> strings = Lists.transform(values, new Function<Integer, String>() { @Override public String apply(Integer i) { System.out.println("convert " + i); return String.valueOf(i); } }); System.out.println(strings); System.out.println(strings); // 出力結果 convert 1 convert 2 convert 3 [1, 2, 3] convert 1 convert 2 convert 3 [1, 2, 3]
なんと、同じ要素に対して複数回 function が適用されています。for ループや、List#get で要素を取得した時にも function が適用されます。
従って、function の処理に時間がかかるとして、 Lists.transform で得られたリストを複数回ループしたり get(index) したりすると何度も function が適用され、アプリケーションのパフォーマンスに影響が出ます。
これは Sets.filter や Maps.asMap, Collections2.transform や Collection2.filter も同様の挙動です。
関数型チックに書いて満足してもパフォーマンスは劣化してますよ、みたいなことにならないよう気をつけましょうというお話でした。
現場からは以上です。
- 作者: 田中裕一
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/27
- メディア: 大型本
- この商品を含むブログ (1件) を見る