Programming Diary.

ソフトウェアエンジニアの技術blog

Javaコレクションフレームワークの比較 (JDK8/GS Collections/Guava)

はじめに

Java Day Tokyo 2015JJUG CCC 2015 Springで紹介されていたGS Collectionsを使用してみました。実例を用いてJava8のStream APIGuavaの使用方法と比較します。

環境
  • JDK 1.8.0_25
  • GS Collections 6.1.0
  • Guava 18.0
  • JUnit 4.8.1
問題設定
  1. 社員のリストから営業部に所属する社員を抽出し、社員番号の昇順で社員名を取得する
  2. 社員のリストから部署ごとの社員数を求める

※『Javaエンジニア養成読本』特集2の第3章「Stream API を使いこなすために」の例を参考にしました。

社員
public class Employee {
    private int id; // 社員番号
    private String name; // 名前
    private Dept dept; // 部署

    // 以下、コンストラクタ・getter
}
部署
public enum Dept {
    ACCOUNTS("経理部"), //
    GENERAL_AFFAIRS("総務部"), //
    HUMAN_RESOURCES("人事部"), //
    SALES("営業部");

    private String name; // 名前

    // 以下、コンストラクタ・getter
}
テストデータ
public abstract class EmployeeDomainForTest {
    protected List<Employee> employeeList;
    
    @Before
    public void setUp(){
        this.setUpEmployeeList();
    }

    private void setUpEmployeeList() {
        this.employeeList = new ArrayList<>();
        this.employeeList.add(new Employee(1003, "Bob", Dept.SALES));
        this.employeeList.add(new Employee(1007, "Fred", Dept.HUMAN_RESOURCES));
        this.employeeList.add(new Employee(1001, "Daniel", Dept.SALES));
        this.employeeList.add(new Employee(1010, "Randy", Dept.GENERAL_AFFAIRS));
        this.employeeList.add(new Employee(1004, "Matt", Dept.ACCOUNTS));
        this.employeeList.add(new Employee(1009, "Tim", Dept.SALES));
        this.employeeList.add(new Employee(1006, "Susan", Dept.GENERAL_AFFAIRS));
        this.employeeList.add(new Employee(1002, "Julia", Dept.HUMAN_RESOURCES));
        this.employeeList.add(new Employee(1005, "Brown", Dept.HUMAN_RESOURCES));
        this.employeeList.add(new Employee(1008, "Kevin", Dept.SALES));
    }
}

実例1

社員のリストから営業部に所属する社員を抽出し、社員番号の昇順で社員名を取得する

  • JDK8
public class JDK8Test extends EmployeeDomainForTest {
    @Test
    public void getEmployeeNamesInSalesSortedById() {
        List<String> result = this.employeeList.stream()
                .filter(e -> e.getDept() == Dept.SALES)
                .sorted(Comparator.comparingInt(Employee::getId))
                .map(Employee::getName).collect(Collectors.toList());

        assertThat(result, is(Arrays.asList("Daniel", "Bob", "Kevin", "Tim")));
    }
}
  • GS Collections
public class GSCollectionsTest extends EmployeeDomainForTest {
    @Test
    public void getEmployeeNamesInSalesSortedById() {
        MutableList<String> result = FastList
                .newList(this.employeeList)
                .selectWith(Predicates2.attributeEqual(Employee::getDept), Dept.SALES)
                .sortThisBy(Employee::getId).collect(Employee::getName);

        assertThat(result, is(Arrays.asList("Daniel", "Bob", "Kevin", "Tim")));
    }
}
  • Guava
public class GuavaTest extends EmployeeDomainForTest {
    @Test
    public void getEmployeeNamesInSalesSortedById() {
        List<Employee> filtered = FluentIterable.from(this.employeeList)
                .filter(e -> e.getDept() == Dept.SALES)
                .toSortedList(Comparator.comparingInt(Employee::getId));
        List<String> result = FluentIterable.from(filtered)
                .transform(Employee::getName).toList();

        assertThat(result, is(Arrays.asList("Daniel", "Bob", "Kevin", "Tim")));
    }
}
メソッド対応表

抽出

クラスメソッド
JDK8Streamfilter(Predicate<? super T> predicate)
GS CollectionsMutableListselect(Predicate<? super T> predicate)
selectWith(Predicate2<? super T,? super P> predicate,P parameter)
GuavaFluentIterablefilter(Predicate<? super E> predicate)
Iterablesfilter(Iterable<T> unfiltered, Predicate<? super T> predicate)

並び替え

クラスメソッド
JDK8Streamsorted()
sorted(Comparator<? super T> comparator)
GS CollectionsMutableListsortThis()
sortThis(Comparator<? super T> comparator)
sortThisBy(Function<? super T,? extends V> function)
GuavaFluentIterabletoSortedList(Comparator<? super E> comparator)

変換

クラスメソッド
JDK8Streammap(Function<? super T,? extends R> mapper)
GS CollectionsMutableListcollect(Function<? super T,? extends V> function)
collectWith(Function2<? super T,? super P,? extends V> function, P parameter)
GuavaFluentIterabletransform(Function<? super E,T> function)
Iterablestransform(Iterable<F> fromIterable, Function<? super F,? extends T> function)
  • JDK8では、はじめにStreamを生成し、抽出・並び替え・変換の各種操作後、最後に結果を返すという処理の流れになる

  • GS Collectionsの場合は、抽出・並び替え・変換の各種メソッドはMutableListなどから直接呼べる(つまり、JDK8の場合のようにはじめにStreamを生成する必要はない。ただし、今回の例ではテストデータemployeeListが標準のListのため、はじめにMutableListを生成する必要がある)

    • GS Collectionsには、他にもImmutableListや遅延評価のLazyIterableなどもあり、一部のメソッドを除きMutableListと同様に使用できる
  • GuavaではFluentIterableまたはIterablesを用いることができるが、FluentIterableはメソッドチェーンでより簡潔に記述できる

実例2

社員のリストから部署ごとの社員数を求める

JDK8
public class JDK8Test extends EmployeeDomainForTest {
    @Test
    public void getEmployeeCountByDept() {
        Map<Dept, Long> result = this.employeeList.stream().collect(
                Collectors.groupingBy(Employee::getDept, Collectors.counting()));

        assertThat(result.get(Dept.ACCOUNTS), is(1L));
        assertThat(result.get(Dept.GENERAL_AFFAIRS), is(2L));
        assertThat(result.get(Dept.HUMAN_RESOURCES), is(3L));
        assertThat(result.get(Dept.SALES), is(4L));
    }
}
GS Collections
public class GSCollectionsTest extends EmployeeDomainForTest {
    @Test
    public void getEmployeeCountsByDept() {
        Bag<Dept> result = FastList.newList(this.employeeList)
                .collect(Employee::getDept).toBag();

        assertThat(result.occurrencesOf(Dept.ACCOUNTS), is(1));
        assertThat(result.occurrencesOf(Dept.GENERAL_AFFAIRS), is(2));
        assertThat(result.occurrencesOf(Dept.HUMAN_RESOURCES), is(3));
        assertThat(result.occurrencesOf(Dept.SALES), is(4));
    }
}
Guava
public class GuavaTest extends EmployeeDomainForTest {
    @Test
    public void getEmployeeCountsByDept() {
        Iterable<Dept> transformed = Iterables.transform(this.employeeList,
             Employee::getDept);
        Multiset<Dept> result = HashMultiset.create(transformed);

        assertThat(result.count(Dept.ACCOUNTS), is(1));
        assertThat(result.count(Dept.GENERAL_AFFAIRS), is(2));
        assertThat(result.count(Dept.HUMAN_RESOURCES), is(3));
        assertThat(result.count(Dept.SALES), is(4));
    }
}
メソッド対応表

要素の出現回数

クラスメソッド
JDK8--
GS CollectionsBagoccurrencesOf(Object item)
GuavaMultisetcount(Object element)
  • GS CollectionsとGuavaでは、それぞれBag, Multiset(要素の重複を許可するSet)を用いることで要素ごとの出現回数を求めることができる
  • JDKには上記に相当する型はないが、Collectors#groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)の第2引数にCollectors#counting()を渡すことによって、グループごとの出現回数をMapで取得できる

まとめ

実例を用いてJDK8, GS Collections, Guavaの使用方法の一部を確認しました。すべてを併用する状況はあまりないかもしれませんが、それぞれ適切に使い分けられるように違いを把握しておくことが大切だと思います。今回の例では使用しなかったメソッドもまだまだたくさんあるので、さらに理解を深めていきたいです。

Javaエンジニア養成読本[現場で役立つ最新知識、満載!]

Javaエンジニア養成読本[現場で役立つ最新知識、満載!]