More than 1 year has passed since last update.

Java Magazine で取り上げられていて存在は知っていたものの、特にチェックはしてなかった GS Collections。

Java Day Tokyo 2015JJUG 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 より前の後方互換を撤廃する大きな変更があるらしい。

GS Collections とは

  • Goldman Sachs (ゴールドマン・サックス)という企業が内部で開発したコレクションフレームワーク。
  • 2012 年にオープンソースとして公開された(goldmansachs/gs-collections | GitHub)。
  • JDK 標準のコレクション(ArrayListHashMap など)と比べると、以下のような特徴がある。
    • 標準のコレクションフレームワークには存在しないコレクションが追加されている(BagsBiMapImmutableList などなど)。
    • メモリ効率が最適化されている。
    • Smalltalk に存在した selectrejectcollect などの便利なメソッドが取り入れられ、 Stream API よりも簡潔にコレクションを操作できる。

インストール

Eclipse Collections はこっち

build.gradle
    compile 'org.eclipse.collections:eclipse-collections:7.0.0'

Maven Repository: eclipse-collections

build.gradle
    compile 'com.goldmansachs:gs-collections:6.1.0'

Maven Repository: com.goldmansachs

Hello World

Main.java
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 は以下のようにインターフェースが定義されている。

gscollections.jpg

青色が 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 の ArrayListHashSetHashMap に当たるクラスは、 GS Collections では FastListUnifiedSetUnifiedMap になる。

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"));
    }
}

UnifiedMapnewWithKeysValues(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 でも、以下のように実装することで遅延反復が実現できる。

GSCollectionsでの遅延反復
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() と同じ要領で作成した文字列を追記する。
  • PrintStreamWriterStringBuilder などが 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 の場合、値は重複を取り除かれた状態で保存される。
    • ListBag は、値が重複したまま保存される。
    • List は登録した順序も保存されるが、 Bag は順序が保存されない。

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.