GS Collections 道場（関ジャバ）で新たに学んだこととか

2015/06/13、関ジャバ主催の [GS Collections 道場[ハンズオン][OSS コレクションフレームワーク] - connpass](http://kanjava.connpass.com/event/15392/?utm_campaign=event_reminder&utm_source=notifications&utm_medium=email&utm_content=title_link) に行ってきたときに、新たに学んだこととかのメモ。

#Predicates
`Predicate`（`boolean` を返す関数インターフェース）を生成するためのスタティックメソッドがたくさん定義されたクラス。

```java:
import java.util.List;

import com.gs.collections.impl.block.factory.Predicates;
import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
        List<Integer> list = Lists.mutable
                                  .of(1, 5, 10, 15)
                                  .select(Predicates.lessThan(10));
        
        System.out.println(list);
    }
}
```

```text:実行結果
[1, 5]
```

Java 8 より前の環境で、ラムダ式が使えないときに便利そう。

[Javadoc](http://www.goldmansachs.com/gs-collections/javadoc/6.1.0/com/gs/collections/impl/block/factory/Predicates.html) を眺めると、便利そうなメソッドがちらほらと。。。

```java:
import java.util.List;

import static com.gs.collections.impl.block.factory.Predicates.*;

import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
        List<String> list = Lists.mutable
                                 .of("one", "two", "three", "four")
                                 .select(in("two", "four"));
        
        System.out.println(list);
    }
}
```

```text:実行結果
[two, four]
```

static インポートすれば、さらにコードが簡潔になって綺麗な感じ。

あと、メソッド参照と組み合わせて使うタイプのものもある。

```java:
import java.util.List;

import static com.gs.collections.impl.block.factory.Predicates.*;

import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
        List<String> list = Lists.mutable
                                 .of("one", "two", "three", "four")
                                 .select(attributeLessThan(String::length, 5));
                               //.select(str -> str.length() < 5); と同じ
        
        System.out.println(list);
    }
}
```

```text:実行結果
[one, two, four]
```

この場合は、ラムダ式の方が良いと思う人もいるかも知れない（自分とか）。

##Java 7 以下の環境でメソッド参照もどきを作る
メソッド参照が使えない環境でも、以下のように工夫することでメソッド参照もどきが作れる。

```java:Person.java
import com.gs.collections.api.block.function.Function;

public class Person {
    
    public static Function<Person, Integer> GET_AGE = new Function<Person, Integer>() {
        @Override
        public Integer valueOf(Person hoge) {
            return hoge.age;
        }
    };
    
    private int age;
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public Person age(int age) {
        this.age = age;
        return this;
    }
    
    @Override
    public String toString() {
        return this.name + "(" + this.age + ")";
    }
}
```

```java:Main.java
import java.util.List;

import static com.gs.collections.impl.block.factory.Predicates.*;

import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
        List<Person> list = Lists.mutable
                                 .of(
                                     person("Sato").age(18),
                                     person("Suzuki").age(26),
                                     person("Tanaka").age(24)
                                 )
                                 .select(attributeLessThan(Person.GET_AGE, 25));
        
        System.out.println(list);
    }
    
    private static Person person(String name) {
        return new Person(name);
    }
}
```

```text:実行結果
[Sato(18), Tanaka(24)]
```

クラスに static な `Function` を用意しておくことで、 Java 7 以下の環境でもメソッド参照のような感じで実装ができるようになる。

#Target
`List` を `Set` に変換する方法として、 `toSet()` というメソッドが用意されている。

```java:toSet()を使って
import java.util.Set;

import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
            Set<Integer> set = Lists.mutable
                                    .of("one", "two", "three")
                                    .collect(String::length)
                                    .toSet();
            
            System.out.println(set);
    }
}
```

```text:実行結果
[3, 5]
```

この方法は単純なのだけど、デメリットとして `collect()` したときと `toSet()` したときで２度ループが回ってしまう。

もし１回のループの中で、 `collect()` と `Set` への置換えを一緒にやりたい場合は、 Target を指定できるメソッドを使用する。

```java:Targetを指定する
import java.util.Set;

import com.gs.collections.impl.factory.Lists;
import com.gs.collections.impl.factory.Sets;

public class Main {
    public static void main(String... args) throws Exception {
            Set<Integer> set = Lists.mutable
                                    .of("one", "two", "three")
                                    .collect(String::length, Sets.mutable.of());
            
            System.out.println(set);
    }
}
```

```text:実行結果
[3, 5]
```

Target は、イテレーションメソッドの第二引数に、置き換え先コレクションのインスタンスを渡すことで指定できる。
こうすることで、 `collect()` でループが回っているときに合わせて第二引数で指定したコレクションに値が順次セットされるようになる。

#groupByEach()
```java:Person.java
import java.util.Arrays;
import java.util.List;

public class Person {
    private String name;
    private List<String> foods;

    public Person(String name) {
        this.name = name;
    }

    public Person likes(String... values) {
        this.foods = Arrays.asList(values);
        return this;
    }

    public List<String> getFavoriteFoods() {
        return foods;
    }

    @Override
    public String toString() {
        return this.name;
    }
}
```

```java:
import com.gs.collections.api.multimap.Multimap;
import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
        Multimap<String, Person> map =
                Lists.mutable
                     .of(
                         person("Sato").likes("rice", "bread", "fish"),
                         person("Suzuki").likes("bread", "ramen"),
                         person("Tanaka").likes("rice", "ramen")
                     )
                     .groupByEach(Person::getFavoriteFoods);

        System.out.println(map);
    }


    private static Person person(String name) {
        return new Person(name);
    }
}
```

```text:実行結果
{ramen=[Suzuki, Tanaka], rice=[Sato, Tanaka], bread=[Sato, Suzuki], fish=[Sato]}
```

- `groupBy()` は、単一の値で集約していた。
- `groupByEach()` は、引数で渡した `Function` から `Iterable` を受け取り、要素１つ１つで集約を行う。

#fixedList
`Lists.mutable`、 `Lists.immutable` と並んで IDE の補完候補に出てくる `Lists.fixedList`。
「要素数が数個で固定されていて、徹底的にメモリ使用量を減らしたい」ときに使うらしい。

例えば、次のように `fixedList` を定義した場合。

```java:
Lists.fixedList.of("aaa", "bbb");
```

この `of()` の実装は次のようになっている。

```java:FixedSizeListFactoryImpl.java
    public <T> FixedSizeList<T> of(T one, T two)
    {
        return this.with(one, two);
    }

    public <T> FixedSizeList<T> with(T one, T two)
    {
        return new DoubletonList<T>(one, two);
    }
```

`DoubletonList` というクラスを生成している。
これの実装を見に行くと、、、

```java:DoubletonList.java
@NotThreadSafe
final class DoubletonList<T> extends AbstractMemoryEfficientMutableList<T> implements Externalizable
{
    private static final long serialVersionUID = 1L;

    private T element1;
    private T element2;

    DoubletonList(T obj1, T obj2)
    {
        this.element1 = obj1;
        this.element2 = obj2;
    }
```

もはや要素は配列すら使わず保持されている。
配列の生成に使われるメモリすら惜しい場合に使うということか・・・。

要素数は宣言したときのサイズで固定されているので、要素の追加や削除はできない（実行時に例外がスローされる）。
要素の上書きはできる（immutable ではない）。

ちなみに、 `Lists.fixedSize.of()` には可変長引数を受け取るものも定義されている。
その場合はどうなっているかというと、、、

```java:FixedSizeListFactoryImpl.java
    public <T> FixedSizeList<T> with(T... items)
    {
        switch (items.length)
        {
            case 0:
                return this.of();
            case 1:
                return this.of(items[0]);
            case 2:
                return this.of(items[0], items[1]);
            case 3:
                return this.of(items[0], items[1], items[2]);
            case 4:
                return this.of(items[0], items[1], items[2], items[3]);
            case 5:
                return this.of(items[0], items[1], items[2], items[3], items[4]);
            case 6:
                return this.of(items[0], items[1], items[2], items[3], items[4], items[5]);
            default:
                return ArrayAdapter.newArrayWith(items);
        }
    }
```

引数が６個より多くなったら、 `ArrayAdapter` を使う処理に切り替わっている。
こいつは、内部で配列を使って値を保持している。

なので、本気でメモリ使用量を抑えたいなら、要素数は 6 個以下にしたほうがいいのだろう。

#ArrayAdapter, ArrayIterate, ListAdapter, ListIterate
配列や SDK のコレクションを、 GS Collections のコレクションに変換するためのクラス。
主に、既存コードに GS Collections を導入するときの橋渡しとして利用する。

```java:
import com.gs.collections.impl.list.fixed.ArrayAdapter;
import com.gs.collections.impl.utility.ArrayIterate;

public class Main {
    public static void main(String... args) throws Exception {
        String[] array = {"one", "two", "three"};
        
        ArrayAdapter.adapt(array)
                    .select(s -> s.contains("e"))
                    .collect(s -> "<" + s + ">")
                    .each(System.out::println);
        
        ArrayIterate.select(array, s -> s.contains("o"))
                    .collect(s -> "[" + s + "]")
                    .each(System.out::println);
    }
}
```

```text:実行結果
<one>
<three>
[one]
[two]
```

- `ArrayAdapter#adapt()` で、配列を GS Collections のコレクションに変換できる。
- `ArrayIterate` は、変換の１ステップを飛ばしていきなり GS Collections のメソッドを使える。
- 変換するだけなら `ArrayAdapter` を使い、それ以外の場合は好きな方を選ぶ感じ（個人的には `Adapter` の方が好きかもしれない）。

`ListAdapter` と `ListIterate` も同じ。

```java:
import java.util.Arrays;
import java.util.List;

import com.gs.collections.impl.list.mutable.ListAdapter;
import com.gs.collections.impl.utility.ListIterate;

public class Main {
    public static void main(String... args) throws Exception {
        List<String> list = Arrays.asList("one", "two", "three");
        
        ListAdapter.adapt(list)
                   .select(s -> s.contains("e"))
                   .collect(s -> "<" + s + ">")
                   .each(System.out::println);
        
        ListIterate.select(list, s -> s.contains("o"))
                   .collect(s -> "[" + s + "]")
                   .each(System.out::println);
    }
}
```

```text:実行結果
<one>
<three>
[one]
[two]
```

#チェック例外用のユーティリティ
LT で急遽紹介されたユーティリティ。

```java:
import java.io.File;
import java.nio.file.Files;

import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
            Lists.mutable
                 .of(new File("hoge.txt"), new File("fuga.txt"))
                 .collect(File::toPath)
                 .flatCollect(path -> {
                     try {
                        return Files.readAllLines(path);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                 })
                 .each(System.out::println);
    }
}
```

`hoge.txt` と `fuga.txt` を読み込んで、全行出力しようとしているけど、 `Files#readAllLines()` が `IOException` をスローするせいでラムダ式が非常に残念なことになっている。

これに、 GS Collections が提供するユーティリティを使うと。。。

```java:
import java.io.File;
import java.nio.file.Files;

import static com.gs.collections.impl.block.factory.Functions.*;
import com.gs.collections.impl.factory.Lists;

public class Main {
    public static void main(String... args) throws Exception {
            Lists.mutable
                 .of(new File("hoge.txt"), new File("fuga.txt"))
                 .collect(File::toPath)
                 .flatCollect(throwing(Files::readAllLines))
                 .each(System.out::println);
    }
}
```

`Functions#throwing()` を間にかますことで、 `try-catch` を書かなくて良くなる！
もしチェック例外がスローされると、 `Functions#throwing()` は発生した例外を `RuntimeException` に詰めてスローし直す。

ところで、この実装を Eclipse でやろうとすると、エディタ上はコンパイルエラーが発生する。

![gscollections.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/6848e5d3-179a-1347-330c-c5dcf3412403.jpeg)

でも、 Gradle とかでビルドして動かすと、普通に動いてくれる。
IntelliJ だとエラーにならない。。。
Eclipse がんばれ。。。


#プリミティブのコレクションは自動生成
GS Collections には、 `DoubleArrayList` とか `IntArrayList` とか、プリミティブ型の値をオートボクシングせずにそのまま保持するコレクションが用意されている。

これらのコードは、 `Double` とか `Int` のところだけ差し替えれば、後は全部同じ実装になっている。
なので、これらのクラスはテンプレートを使ってビルド時に動的に生成されている。

テンプレートのファイルは `gs-collections-code-generator` の下の、 [src/main/resources の下](https://github.com/goldmansachs/gs-collections/tree/master/gs-collections-code-generator/src/main/resources) にある。

例えば、 `*ArraryList` のテンプレートは以下のようになっている。

```java:src/main/resources/impl/list/mutable/primitiveArrayList.stg
@NotThreadSafe
public class <name>ArrayList extends Abstract<name>Iterable
        implements Mutable<name>List, Externalizable
{
    private static final long serialVersionUID = 1L;
    private static final <type>[] DEFAULT_SIZED_EMPTY_ARRAY = {};
    private static final <type>[] ZERO_SIZED_ARRAY = {};
    private static final int MAXIMUM_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    protected int size;
    protected transient <type>[] items = DEFAULT_SIZED_EMPTY_ARRAY;

    public <name>ArrayList()
    {
    }
```

`<name>` のところに `Double` とか `Int` が入って、 `<type>` のところに `double` とか `int` が入るのだと思う。

----

**その他所感等**

- 何をどうすれば良いか分からない場面があったので、参加して直接ご質問できて良かったです。
- Kata は、 GS Collections だけでなく、ラムダ式やメソッド参照に慣れるための教材としても良さそうだと思いました。
    - 個人的にメソッド参照を使うクセが付いておらず、ついラムダ式でダラダラ書こうとしてしまうので、今回のハンズオンでメソッド参照使うクセがついてきた感じがしました。  
（たぶん、 Eclipse にメソッド参照の補完がないのが原因かと。 IntelliJ にちょっと興味を持ち出した。。。）
    - コメントを日本語化したら、社内での勉強会でも利用できそうだなぁと思ったりしたり。
- 上記にまとめたように、新たに得られたものが多く、とても良い経験ができました。

#参考
- [Overview (GS Collections - 6.1.0)](http://www.goldmansachs.com/gs-collections/javadoc/6.1.0/index.html)
- [ラーメン - Wikipedia](http://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%BC%E3%83%A1%E3%83%B3)