Java Magazine で取り上げられていて存在は知っていたものの、特にチェックはしてなかった GS Collections。
Java Day Tokyo 2015 や JJUG CCC 2015 で再びその名前を耳にし、気になってチラッと実装を見たところ、面白そうだったので使い方を調べてみた。
【追記】Eclipse Collections
- 2015 年の 10 月から 12 月にかけて、 GS Collections は Eclipse 財団に移管された。
- 名前も GS Collections から Eclipse Collections に変更された。
- 公式サイトはこちら
→ Eclipse Collections - Features you want with the collections you need. (日本語ページ) - GS Collections と、機能的な差はない。
- パッケージ名は変わってる。
- コントリビュータを広く募集するようになった(今までは issue の受付はやっていたけど、コントリビュータの受付はしていなかったぽい)。
- 2016 年中に ver 8 をリリースする予定で、 Java 8 より前の後方互換を撤廃する大きな変更があるらしい。
- そのへんの話は、JJUG CCC 2015 Fall の資料 に載ってる。
#GS Collections とは
- Goldman Sachs (ゴールドマン・サックス)という企業が内部で開発したコレクションフレームワーク。
- 2012 年にオープンソースとして公開された(goldmansachs/gs-collections | GitHub)。
- JDK 標準のコレクション(
ArrayList
やHashMap
など)と比べると、以下のような特徴がある。- 標準のコレクションフレームワークには存在しないコレクションが追加されている(
Bags
、BiMap
、ImmutableList
などなど)。 - メモリ効率が最適化されている。
- Smalltalk に存在した
select
やreject
、collect
などの便利なメソッドが取り入れられ、 Stream API よりも簡潔にコレクションを操作できる。
- 標準のコレクションフレームワークには存在しないコレクションが追加されている(
#インストール
Eclipse Collections はこっち
compile 'org.eclipse.collections:eclipse-collections:7.0.0'
Maven Repository: eclipse-collections
compile 'com.goldmansachs:gs-collections:6.1.0'
Maven Repository: com.goldmansachs
#Hello World
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
Lists.immutable
.of("one", "two", "three", "four")
.select(s -> s.contains("o"))
.collect(s -> "[" + s + "]")
.each(System.out::println);
}
}
[one]
[two]
[four]
#Mutable と Immutable
GS Collections のコレクションは、その性質から Mutable と Immutable の2つに大きく分けることができる。
Mutable はコレクションの要素を変更するための add()
などの更新系のメソッドが定義されている。
しかし、Immutable なコレクションにはそれら更新系のメソッドが一切定義されていない。
例えば、 List
の Mutable と Immutable は以下のようにインターフェースが定義されている。
青色が JDK 付属のインターフェース。緑色が GS Collections で定義されているインターフェースやクラス。
MutableList
は、 JDK の List
インターフェースを継承しているため、 add()
などの更新系メソッドを持つ。
一方で、 ImmutableList
は JDK で定義されているインターフェースのうち、 Iterable
しか継承していない。そして親インターフェースである ListIterable
や、さらにその親達のインターフェースには、更新系のメソッドが定義されていない。このため、 ImmutableList
型の変数を定義すると、更新が一切できない List
ができあがる。
一応、JDK でも Collections.unmodifiableList()
などのメソッドを使うことで Immutable なコレクションを作成できる。
しかし、このメソッドは戻り値の型が List
になっている。このため add()
などの更新系のメソッドを呼ぶことができてしまう。その場合、コンパイルエラーは起こらず、実行時に UnsupportedOperationException
がスローされる。
つまり、間違って更新系のメソッドを呼んでしまっていても、実行するまでミスに気づけないという問題点がある。
一方、 ImmutableList
ならそもそも更新系のメソッドが定義されていないので、ミスすることはない(コンパイルエラーになる)。
また、型も Immutable
と銘打たれているので、値の使い回しが容易になったり、マルチスレッドプログラミングで安心して利用できるといったメリットがある(List
型という情報だけだと Imutable かどうかわからず、使い回しやマルチスレッドでの利用がしづらくなる)。
#どの型の変数に代入すべきか
JDK のコレクションフレームワークでは、リストなら List
型に、セットなら Set
型に、マップなら Map
型の変数に代入するのが良いとされていた。
GS Collections ならどうすべきか。
上述の Mutable, Immutable の関係を考えるに、以下のようになるかと思う(個人的な見解です)。
List, Set, Map などコレクションの具体的な種類を意識する?
|
+-- <NO> -->変更可能かどうか意識する?
| |
<YES> +-- <NO> --> RichIterable
| |
| +-- <YES> --> MutableCollection, ImmutableCollection
|
変更可能かどうか意識する?
|
+-- <NO> --> ListIterable, SetIterable, MapIterable...
|
+-- <YES> --> MutableList, MutableSet, MutableMap...
ImmutableList, ImmutableSet, ImmutableMap...
#基本的なクラスを使う
JDK の ArrayList
、HashSet
、HashMap
に当たるクラスは、 GS Collections では FastList
、UnifiedSet
、UnifiedMap
になる。
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.map.mutable.UnifiedMap;
import com.gs.collections.impl.set.mutable.UnifiedSet;
public class Main {
public static void main(String[] args) {
List<String> list = new FastList<>();
Set<String> set = new UnifiedSet<>();
Map<String, String> map = new UnifiedMap<>();
}
}
これらのクラスには、 static なファクトリメソッドが用意されている。
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.map.mutable.UnifiedMap;
import com.gs.collections.impl.set.mutable.UnifiedSet;
import com.gs.collections.impl.tuple.Tuples;
public class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List<String> list = FastList.newListWith("hoge", "fuga", "piyo");
Set<String> set = UnifiedSet.newSetWith("hoge", "fuga", "piyo");
Map<String, String> map = UnifiedMap.newWithKeysValues(
"hoge", "HOGE",
"fuga", "FUGA",
"piyo", "PIYO");
map = UnifiedMap.newMapWith(
Tuples.pair("hoge", "HOGE"),
Tuples.pair("fuga", "FUGA"),
Tuples.pair("piyo", "PIYO"));
}
}
UnifiedMap
は newWithKeysValues(key1, value1, key2, value2)
という形で初期化できるが、エントリの数が4つまでしか対応していない。
それより多い場合は newMapWith(Pair...)
を使う。
また、各コレクションにはファクトリ用のクラスが用意されていて、それを利用することもできる。
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.gs.collections.impl.factory.Lists;
import com.gs.collections.impl.factory.Maps;
import com.gs.collections.impl.factory.Sets;
public class Main {
public static void main(String[] args) {
List<String> list = Lists.mutable.of("hoge", "fuga", "piyo");
Set<String> set = Sets.mutable.of("hoge", "fuga", "piyo");
Map<String, String> map = Maps.mutable.of("hoge", "HOGE", "fuga", "FUGa", "piyo", "PIYO");
}
}
#基本的なイテレーションパターン
##select
import java.util.List;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
List<String> list = Lists.mutable
.of("hoge", "fuga", "piyo")
.select(s -> s.contains("g"));
System.out.println(list);
}
}
[hoge, fuga]
- 条件に一致する要素だけを抽出する。
##reject
import java.util.List;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
List<String> list = Lists.mutable
.of("hoge", "fuga", "piyo")
.reject(s -> s.contains("g"));
System.out.println(list);
}
}
[piyo]
- 条件に一致しない要素だけを抽出する。
##partition
import com.gs.collections.api.partition.list.PartitionMutableList;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
PartitionMutableList<String> list = Lists.mutable
.of("hoge", "fuga", "piyo")
.partition(s -> s.contains("g"));
System.out.println(list.getSelected());
System.out.println(list.getRejected());
}
}
[hoge, fuga]
[piyo]
- 条件に一致した要素と、そうでない要素とで分割する。
##collect
import java.util.List;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
List<Integer> list = Lists.mutable
.of("one", "two", "three")
.collect(s -> s.length());
System.out.println(list);
}
}
[3, 3, 5]
- 各要素を変換して、新しい List を生成する。
##flatCollect
import java.util.List;
import com.gs.collections.impl.factory.Lists;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.tuple.Tuples;
public class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List<String> list = Lists.mutable
.of(Tuples.pair("hoge", "HOGE"),
Tuples.pair("fuga", "FUGA"),
Tuples.pair("piyo", "PIYO"))
.flatCollect(pair -> FastList.newListWith(pair.getOne(), pair.getTwo()));
System.out.println(list);
}
}
[hoge, HOGE, fuga, FUGA, piyo, PIYO]
- コレクションの各要素に対して
flatCollect()
に渡したラムダ式が適用される。 - ラムダ式は
Iterable
を実装した値を返すようにする。 - すると、ラムダ式が返した
Iterable
をすべて連結した新しいList
が返される。 - つまり、フラットな状態にしている。
##groupBy
import com.gs.collections.api.multimap.Multimap;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
Multimap<Integer, String> map = Lists.mutable
.of("one", "two", "three", "four", "five")
.groupBy(s -> s.length());
System.out.println(map);
}
}
{3=[one, two], 4=[four, five], 5=[three]}
-
groupBy()
に渡したラムダ式が返した値で、コレクションの各要素を集約する。 - 戻り値は
Multimap
で返される(キー1つに対して、複数の値が設定できるMap
)。
##detect
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("one", "two", "three");
System.out.println(list.detect(s -> s.length() == 3));
System.out.println(list.detect(s -> s.length() == 6));
}
}
one
null
-
detect()
の引数で渡したラムダ式がコレクションの各要素に対して適用される。 - 1回でも
true
を返した場合は、最初にtrue
を返した要素が返される。 - 1回も
true
が返されない場合は、null
が返される。
##detectIfNone
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("one", "two", "three");
System.out.println(list.detectIfNone(s -> s.length() == 3, () -> "default value"));
System.out.println(list.detectIfNone(s -> s.length() == 6, () -> "default value"));
}
}
one
default value
- 第一引数のラムダ式がすべての要素に対して
false
を返した場合、第二引数のラムダ式が返す値が返される。
##anySatisfy
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("one", "two", "three");
System.out.println(list.anySatisfy(s -> s.contains("r")));
System.out.println(list.anySatisfy(s -> s.contains("z")));
}
}
true
false
- 1回でもラムダ式が
true
を返したら、true
を返す。
##allSatisfy
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("hoge", "fuga", "piyo");
System.out.println(list.allSatisfy(s -> s.length() == 4));
System.out.println(list.allSatisfy(s -> s.contains("o")));
}
}
true
false
- すべての要素に対してラムダ式が
true
を返したときだけ、true
を返す。
##noneSatisfy
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("hoge", "fuga", "piyo");
System.out.println(list.noneSatisfy(s -> s.length() == 5));
System.out.println(list.noneSatisfy(s -> s.contains("o")));
}
}
true
false
- すべての要素に対して、ラムダ式が
false
を返した場合のみ、true
を返す。
##forEach
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("hoge", "fuga", "piyo");
list.forEach(System.out::println);
}
}
※コンパイルエラーで実行できない
-
forEach()
は、JDK のIterable
と GS Collections のInternalIterable
の両方で定義されてしまっている。 - 2つのメソッドは、受け取る引数の型は違うものの、ラムダ式で記述すると全く同じ実装になってしまう。
- このため、コンパイラはメソッドを2つのどちらかを区別することができず、コンパイルエラーになる。
- Java 8 以降の環境でラムダ式を使う場合は、変わりに
each()
メソッドを使う。
##forEachWithIndex
import com.gs.collections.api.list.ListIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
ListIterable<String> list = Lists.mutable.of("hoge", "fuga", "piyo");
list.forEachWithIndex((s, i) -> System.out.printf("[%d] : %s%n", i, s));
}
}
[0] : hoge
[1] : fuga
[2] : piyo
- インデックスとともに内部ループできる。
- これは地味にありがたい(標準の API だけだと、インデックスが必要になった途端初代 for 文に舞い戻る必要があったので)。
##injectInto
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
String result = Lists.mutable.of(1, 2, 3, 4, 5)
.injectInto("0", (memo, element) -> {
System.out.printf("element=%d, memo=\"%s\"%n", element, memo);
return memo + " - " + element;
});
System.out.println(result);
}
}
element=1, memo="0"
element=2, memo="0 - 1"
element=3, memo="0 - 1 - 2"
element=4, memo="0 - 1 - 2 - 3"
element=5, memo="0 - 1 - 2 - 3 - 4"
0 - 1 - 2 - 3 - 4 - 5
- 第一引数が
memo
の初期値となり、第二引数のラムダ式がコレクションの各要素に対して適用される。 - ラムダ式の第一引数には、最初は
injectInto()
メソッドの第一引数の値が、以後はラムダ式が直前にreturn
した値が渡される。 - ラムダ式の第二引数には、コレクションの各要素が順次渡される。
##tap
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
Lists.mutable
.of(1, 2, 3, 4)
.tap(n -> System.out.println("[" + n + "]"))
.select(n -> n % 2 == 0)
.each(n -> System.out.println("<" + n + ">"));
}
}
[1]
[2]
[3]
[4]
<2>
<4>
-
each()
は戻り値がvoid
なのに対して、tap
は戻り値が再びRichIterable
を返すようになっている。 - つまり、要素全体に対して何らかの処理を行った後も処理を続けることができる。
#遅延反復(Lazy iteration)
各イテレーション用のメソッドは、実行するたびに反復処理が実行される。
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
Lists.mutable
.of("one", "two", "three")
.select(s -> {
System.out.println("select : " + s);
return s.contains("t");
})
.collect(s -> {
System.out.println("collect : " + s);
return s.length();
})
.each(l -> {
System.out.println("each : " + l);
});
}
}
select : one
select : two
select : three
collect : two
collect : three
each : 3
each : 5
一方、 Java 8 の Stream API は終端処理を実行するまでループは実行されない遅延反復になっている。
GS Collections でも、以下のように実装することで遅延反復が実現できる。
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
Lists.mutable
.of("one", "two", "three")
+ .asLazy()
.select(s -> {
System.out.println("select : " + s);
return s.contains("t");
})
.collect(s -> {
System.out.println("collect : " + s);
return s.length();
})
.each(l -> {
System.out.println("each : " + l);
});
}
}
select : one
select : two
collect : two
each : 3
select : three
collect : three
each : 5
asLazy()
と入れるだけで、遅延反復になる。
もうちょい分かりやすく。。。
import com.gs.collections.api.RichIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
RichIterable<String> list1 = Lists.mutable.of("one", "two", "three").asLazy();
System.out.println(">>>>>>>>>>");
RichIterable<String> list2 = list1.select(s -> {
System.out.println("select : " + s);
return s.contains("t");
});
System.out.println(">>>>>>>>>>");
RichIterable<Integer> list3 = list2.collect(s -> {
System.out.println("collect : " + s);
return s.length();
});
System.out.println(">>>>>>>>>>");
list3.each(l -> System.out.println("each : " + l));
}
}
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
select : one
select : two
collect : two
each : 3
select : three
collect : three
each : 5
最後に each()
を実行した時に、初めて select()
や collect()
で渡したラムダ式が実行されている。
#その他の便利なメソッド
##makeString
import com.gs.collections.api.RichIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
RichIterable<Integer> ite = Lists.mutable.of(1, 2, 3, 4);
System.out.println(ite.makeString());
System.out.println(ite.makeString(" : "));
System.out.println(ite.makeString("[", ", ", "]"));
}
}
1, 2, 3, 4
1 : 2 : 3 : 4
[1, 2, 3, 4]
-
makeString()
で、半角カンマ区切り、 -
makeString(String)
で、区切り文字指定、 -
makeString(String, String, String)
で、開始・終了文字と区切り文字指定、 - で
Iterable
内部の要素を連結した文字列を生成できる。
##appendString
import java.io.StringWriter;
import com.gs.collections.api.RichIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
RichIterable<Integer> ite = Lists.mutable.of(1, 2, 3, 4);
StringWriter sw = new StringWriter();
ite.appendString(sw, "{", " * " , "}");
System.out.println(sw);
}
}
{1 * 2 * 3 * 4}
- 第一引数でした Appendable に、
makeString()
と同じ要領で作成した文字列を追記する。 -
PrintStream
やWriter
、StringBuilder
などがAppendable
を実装している。
##count
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
System.out.println(Lists.mutable.of(1, 2, 3, 4).count(n -> n % 2 == 0));
}
}
2
- 引数で指定した条件を満たす要素の数を返す。
- 単純に要素数を取得したいときは
size()
メソッドがある。
##getFirst/getLast
import com.gs.collections.api.ordered.OrderedIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
OrderedIterable<Integer> list = Lists.mutable.of(1, 2, 3, 4);
System.out.println(list.getFirst());
System.out.println(list.getLast());
}
}
1
4
- 先頭と末尾の要素を取得できる。
##max/min
import com.gs.collections.api.RichIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
RichIterable<Integer> list = Lists.mutable.of(1, 2, 3, 4);
System.out.println(list.max());
System.out.println(list.min());
}
}
4
1
- 最大と最小の要素を取得できる。
- 引数に Comparator を渡すことで、比較方法を指定することもできる。
##maxBy/minBy
import com.gs.collections.api.RichIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
RichIterable<String> list = Lists.mutable.of("one", "two", "three");
System.out.println(list.max());
System.out.println(list.maxBy(s -> s.length()));
}
}
two
three
- 引数で指定したラムダ式が返した値で順序付けを行い、最大 / 最小の要素を取得する。
##aggregateBy
import java.util.List;
import com.gs.collections.api.map.MapIterable;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
MapIterable<String, List<String>> map =
Lists.mutable
.of(1, 2, 3, 4, 5, 6)
.aggregateBy(
n -> (n % 2 == 0) ? "even" : "odd",
() -> Lists.mutable.empty(),
(list, n) -> {
list.add(String.valueOf(n));
return list;
}
);
System.out.println(map);
}
}
{even=[2, 4, 6], odd=[1, 3, 5]}
- 要素をグループ分けして、新しい
Map
を生成する。 - 第一引数が、グループ分けをするためのラムダ式。このラムダ式が返した値で要素がグループ分けされる。
- 第二引数が、グループごとの初期値を生成するラムダ式。第一引数が新しいグループの分類を返すたびにコールバックされる。
- 第三引数が、グループごとの値を作るためのラムダ式。
- ラムダ式の第一引数には、そのグループが出来た直後の場合は
aggregateBy()
の第二引数のラムダ式が返した初期値が、 - その後は、前回このラムダ式が返した値が渡される。
- 第二引数には、要素の値が順次渡される。
- ラムダ式の第一引数には、そのグループが出来た直後の場合は
##chunk
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
System.out.println(Lists.mutable.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).chunk(4));
}
}
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
- 指定した数ずつになるように要素をできる限り当分割して、分割されてできたコレクションを要素として持つ新しいコレクションを返す。
##zip
import java.util.List;
import com.gs.collections.api.tuple.Pair;
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
List<Pair<String, Integer>> list = Lists.mutable
.of("one", "two", "three", "four")
.zip(Lists.mutable.of(9, 8, 7));
System.out.println(list);
}
}
[one:9, two:8, three:7]
- コレクション同士の各要素を順番に突き合わせて、それぞれの要素を
Pair
に格納し、できたPair
を新しいリストに詰めて返す。 - 要素数が合わない場合は、小さい方に合わされる。
##take/drop
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
System.out.println(Lists.mutable.of(1, 2, 3, 4).take(2));
System.out.println(Lists.mutable.of(1, 2, 3, 4).drop(2));
}
}
[1, 2]
[3, 4]
-
take()
で、指定した数だけ先頭から要素を抽出する。 -
drop()
は後ろから抽出する。
##distinct
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
System.out.println(Lists.mutable.of(1, 1, 2, 3, 3, 4).distinct());
}
}
[1, 2, 3, 4]
- 重複を取り除く。
##with/without
import com.gs.collections.impl.factory.Lists;
public class Main {
public static void main(String[] args) {
Lists.mutable
.of(1, 2, 3)
.without(2)
.with(99)
.each(System.out::println);
}
}
1
3
99
-
with()
は要素を追加、without()
は要素を削除。 -
add()
やremove()
との違いは、流れるようなインターフェースの上で使える、という点。 -
Mutable
なコレクションの場合、実際に要素が追加・削除されている(新しいコレクションが生成されているわけではない)。
#追加のコレクション
##プリミティブコレクション
import com.gs.collections.api.IntIterable;
import com.gs.collections.impl.factory.primitive.IntLists;
public class Main {
public static void main(String[] args) {
IntIterable ite = IntLists.mutable.of(1, 2, 3);
}
}
-
Integer
などのラッパークラスを使用していると、オートボクシングが発生したりでパフォーマンス的によろしくない。 - ということで、プリミティブ型専用の
Iterable
が用意されている。 - 各プリミティブごとに用意されている(例は
int
型のケース)。
##Bag
import com.gs.collections.api.bag.Bag;
import com.gs.collections.impl.factory.Bags;
public class Main {
public static void main(String[] args) {
Bag<String> bag = Bags.mutable.of("hoge", "fuga", "fuga", "piyo", "piyo", "piyo");
System.out.printf("%s は %d 個%n", "fuga", bag.occurrencesOf("fuga"));
bag.forEachWithOccurrences((s, n) -> System.out.printf("%s : %d 個%n", s, n));
System.out.println(bag.toMapOfItemToCount());
}
}
fuga は 2 個
fuga : 2 個
hoge : 1 個
piyo : 3 個
{hoge=1, fuga=2, piyo=3}
- 要素の重複が許可された
Set
のようなもの。 - 重複して登録された要素については、その出現回数を取得することができる。
-
occurrencesOf(要素)
で指定した要素の出現回数を取得できる。 -
forEachWithOccurrences()
で要素と出現回数を引数に受け取りながら反復処理ができる・ -
toMapOfItemToCount()
で、キーに要素、値に出現回数を持ったMap
に変換できる。
##Multimap
import com.gs.collections.api.multimap.MutableMultimap;
import com.gs.collections.impl.factory.Multimaps;
public class Main {
public static void main(String[] args) {
System.out.println("set : " + setup(Multimaps.mutable.set.empty()));
System.out.println("list : " + setup(Multimaps.mutable.list.empty()));
System.out.println("bag : " + setup(Multimaps.mutable.bag.empty()));
}
private static MutableMultimap<String, String> setup(MutableMultimap<String, String> map) {
map.put("one", "xxxx");
map.put("one", "yyyy");
map.put("one", "zzzz");
map.put("one", "xxxx");
map.put("two", "yyyy");
map.put("two", "xxxx");
return map;
}
}
set : {two=[xxxx, yyyy], one=[xxxx, yyyy, zzzz]}
list : {two=[yyyy, xxxx], one=[xxxx, yyyy, zzzz, xxxx]}
bag : {two=[xxxx, yyyy], one=[xxxx, xxxx, yyyy, zzzz]}
- 1つのキーに対して、複数の値を登録できる
Map
。 - 要は、
Map<キー, Collection>
なMap
のイメージ。 - 値を保存するコレクションは
Set
,List
,Bag
のいずれかを選べる。-
Set
の場合、値は重複を取り除かれた状態で保存される。 -
List
とBag
は、値が重複したまま保存される。 -
List
は登録した順序も保存されるが、Bag
は順序が保存されない。
-
#参考