LoginSignup
6
6

More than 5 years have passed since last update.

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

Posted at

2015/06/13、関ジャバ主催の GS Collections 道場[ハンズオン][OSS コレクションフレームワーク] - connpass に行ってきたときに、新たに学んだこととかのメモ。

Predicates

Predicateboolean を返す関数インターフェース)を生成するためのスタティックメソッドがたくさん定義されたクラス。

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);
    }
}
実行結果
[1, 5]

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

Javadoc を眺めると、便利そうなメソッドがちらほらと。。。

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);
    }
}
実行結果
[two, four]

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

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

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);
    }
}
実行結果
[one, two, four]

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

Java 7 以下の環境でメソッド参照もどきを作る

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

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 + ")";
    }
}
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);
    }
}
実行結果
[Sato(18), Tanaka(24)]

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

Target

ListSet に変換する方法として、 toSet() というメソッドが用意されている。

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);
    }
}
実行結果
[3, 5]

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

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

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);
    }
}
実行結果
[3, 5]

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

groupByEach()

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;
    }
}
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);
    }
}
実行結果
{ramen=[Suzuki, Tanaka], rice=[Sato, Tanaka], bread=[Sato, Suzuki], fish=[Sato]}
  • groupBy() は、単一の値で集約していた。
  • groupByEach() は、引数で渡した Function から Iterable を受け取り、要素1つ1つで集約を行う。

fixedList

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

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

Lists.fixedList.of("aaa", "bbb");

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

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 というクラスを生成している。
これの実装を見に行くと、、、

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() には可変長引数を受け取るものも定義されている。
その場合はどうなっているかというと、、、

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

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

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

ArrayAdapter, ArrayIterate, ListAdapter, ListIterate

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

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);
    }
}
実行結果
<one>
<three>
[one]
[two]
  • ArrayAdapter#adapt() で、配列を GS Collections のコレクションに変換できる。
  • ArrayIterate は、変換の1ステップを飛ばしていきなり GS Collections のメソッドを使える。
  • 変換するだけなら ArrayAdapter を使い、それ以外の場合は好きな方を選ぶ感じ(個人的には Adapter の方が好きかもしれない)。

ListAdapterListIterate も同じ。

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);
    }
}
実行結果
<one>
<three>
[one]
[two]

チェック例外用のユーティリティ

LT で急遽紹介されたユーティリティ。

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.txtfuga.txt を読み込んで、全行出力しようとしているけど、 Files#readAllLines()IOException をスローするせいでラムダ式が非常に残念なことになっている。

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

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

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

プリミティブのコレクションは自動生成

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

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

テンプレートのファイルは gs-collections-code-generator の下の、 src/main/resources の下 にある。

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

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 にちょっと興味を持ち出した。。。)
    • コメントを日本語化したら、社内での勉強会でも利用できそうだなぁと思ったりしたり。
  • 上記にまとめたように、新たに得られたものが多く、とても良い経験ができました。

参考

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6