プログラマ的京都生活

2010年02月22日

[][]Google Collections Libraryステキというお話。

Javaしか知らなかった時はまぁ多少冗長だなー位にしか思っていなかったんですが、rubyを知ってからはJavaプログラミング中に「rubyならもっと簡潔にかけるのに・・・」なんて思ったりすることが増えてきました。

例えば、

List<String> animals = new ArrayLists<String>();
animals.add("cat");
animals.add("dog");
animals.add("pig");

っていうJavaのコードは、rubyだと

animals = %w[cat dog pig]

というように一行で書けます。

一行で書けるrubyがすごいと言うよりは、可読性を考えたときに、これは単に動物のリストを宣言したいだけなので、むしろ一行で書けるべきで、3行になってしまうJavaがちょっといただけないのだと思います。

少し細かいですが、他によく言われるのはジェネリクス宣言の冗長さ。List<String>って宣言しているんだからArrayListも<String>に決まってるじゃないかとかね。

あとrubyのブロックを使用したコレクション系の処理はjavaでもこんな風にかけたらなーって思いながらいつもfor文を書いていたりとか。

以下は1〜6の数字から偶数の数字を抽出するrubyプログラムです。

numbers = [1,2,3,4,5,6]
selected_numbers = numbers.select do |num|
  num.even?
end 

こうやれば上と同じコードが1行でかけます。

selected_numbers = [1,2,3,4,5,6].select(&:even?)

要するに1〜6までの数字からselectしますよー、xxxxを満たすものを。という最後の「xxxを満たすものを」という部分だけをプログラムすればよいので、必要なところだけ埋めている感じで気持ちイイのです。

似たような、というと語弊があるかもしれませんが、Googleから09年年末に1.0としてfinalリリースされたgoogle collections libraryを使えばこのあたりの不満が改善され、結構いい感じでプログラミングできそうなことがわかりました。

最初の例だとこんな感じです。

List<String> animals = Lists.newArrayList("cat", "dog", "pig");

可変長引数型推論を使って、1行でかつジェネリクスの宣言の冗長さが改善されています。

リストのフィルタリングの例は次のようになります。

List<Integer> numbers = Lists.newArrayList(1,2,3,4,5,6);
Collection<Integer> filteredCollection = Collections2.filter(numbers, new Predicate<Integer>() {
	@Override
	public boolean apply(Integer input) {
		return input % 2 == 0;
	}
});

Collections2というのがちょっと苦しいですが(でも気持ちは十分分かる)、かなーりいい感じではないでしょうか。

早速仕事で使っちゃおう!

・・・一応書いておくと、他のサイトに書いていましたが、Java7で噂されているクロージャー導入とかジェネリクスの冗長さの改善とかが実現されるとダブルスタンダードみたいになってしまう危険性ありです。とは言え、Java7出るの遅れているし、どうせ出てもなかなかバージョン上げられないし。。それに怯えるよりも今の開発環境をよりよくしたいな、と思います。

そういえば、commons-collection既に使ってるなー。。


もうちょっと詳しく

Collections2、Lists、Sets、Maps

Lists、Collections2を上で書きましたが、もちろんSets、Mapsというのもあります。

加えて、ImmutableList、ImmutableSet、ImmutableMapとかもあって重宝しそう。宣言した後は変更の必要が無いというケースは多々ありますので。

この辺を使って簡単なサンプルを。

Map<String,String> dictionary1 = ImmutableMap.of("dog", "犬", "cat", "猫");
Map<String,String> dictionary2 = ImmutableMap.of("cat", "猫", "pig", "豚");
		
MapDifference<String, String> difference = Maps.difference(dictionary1, dictionary2);
Map<String, String> commonDictionary = difference.entriesInCommon();

dictioanry1とdictionary2を用意し、これらに共通するエントリを持ったcommonDictionaryを作成する例です。もちろん、このMapは「cat:猫」のみです。


FunctionとPredict

これもまずrubyの例を示します。

ary = [1,2,3,4,5].map{|num| num*2}

これでaryは[2,4,6,8,10]となります。これと同様の処理を行うのがFunctionです。先程のPredictの例とまぁまぁ似ています。

List<Integer> numbers = Lists.newArrayList(1,2,3,4,5);
Collection<Integer> transformedList = Collections2.transform(numbers, new Function<Integer, Integer>() {
	@Override
	public Integer apply(Integer from) {
		return from*2;
	}
});

これですべての値を2倍したtransformedListができあがります。Functionのジェネリクスの部分の1番目がインプットの型、2番目がアウトプットの型。

ついでにPredictももう少し。毎回Predicatesを生成しなくてもある程度よく使うものは最初から組み込まれています!

List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 3, 2, 1);
Collection<Integer> filteredList = Collections2.filter(list, Predicates.in(ImmutableList.of(2, 4)));

これは2と4が含まれた長さ3のリストが返ります。

次のはnullを除いたリストを取得する例です。

List<Integer> list = Lists.newArrayList(1, 2, 3, null, 4, 5, 6);		
Collection<Integer> notNullList = Collections2.filter(list, Predicates.notNull());

条件を複数組み合わせることも出来ます。

List<Integer> list = Lists.newArrayList(1, 2, 3, null, 4, 5, 6);		
		
Predicate<Integer> predicates = Predicates.and(Predicates.notNull(), new Predicate<Integer>() {
	@Override
	public boolean apply(Integer input) {
		return input % 2 == 0;
	}
});
Collection<Integer> evenList = Collections2.filter(list, predicates);

nullでない要素でかつ偶数の要素というANDの例ですが、ORももちろんあります。


便利なjoin

地味に重宝しそうなjoinの紹介。ログ出力辺りでよく使いそう。っていうか多分どこのプロジェクトでも既にUtility化してあると思うけど。

List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
String str = Joiner.on(",").join(list);  // 1,2,3,4,5,6

これをfor使ってやろうとすると、最後の1回分をどうするかって処理を書かないといけないんだよね。。

昔それについてのブログ書きました。http://d.hatena.ne.jp/mtoyoshi/20080717/1216299220

Mapについてもできます。素晴らしい。

MapJoiner joiner = Joiner.on(",").withKeyValueSeparator(":");
String str = joiner.join(ImmutableMap.of("cat", "猫", "dog", "犬"));  // cat:猫, dog:犬

Multimap、Multiset、Bimap

これらはJDKにはないクラス。

Multimapはキーに対する値が複数もてるようなMap。つまり値がコレクションってこと。

Multimap<String, String> multiMap = ArrayListMultimap.create();
multiMap.put("cat", "猫");
multiMap.put("cat", "ねこ");
multiMap.put("dog", "犬");
multiMap.put("dog", "いぬ");
multiMap.putAll("pig", Arrays.asList("豚", "ぶた"));

普通のHashMapとかでやると、キーに対する値がnullならArrayListを追加して・・・みたいなコードが必要ですが、そんなの気にせず使えるところがいいですね!

Multisetは名前のとおり、多重集合なので、重複OKです。Bagという名前の方が有名かな、と。

Bimapはキーだけでなく、値もユニークである必要があるMapです。

なので、Map#keysみたいにBimap#valuesというのがあります。また、BiMap#inverseでキーと値が逆転したMapが取得できるので、キー→値だけでなく、値→キーということが実現出来ます。


他にもいろいろありますが、今日はこの辺で。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/mtoyoshi/20100222/1266767160