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は順序が保存されない。 
 - 
 
