グアバでのコレクションのフィルタリングと変換

グアバでのコレクションのフィルタリングと変換

1. 概要

このチュートリアルでは、filter and transform collections with Guavaを実行する方法を説明します。

述語を使用してフィルタリングし、ライブラリが提供する関数を使用して変換し、最後に、フィルタリングと変換の両方を組み合わせる方法を確認します。

参考文献:

Guava 21の新しいストリーム、コンパレータ、コレクター

Guava 21のcommon.collectパッケージに含まれるツールの迅速かつ実用的なガイド。

グアバマルチマップガイド

標準java.util.Mapと比較したGuava Multimapの短いガイド

グアバレンジセットガイド

Google Guava RangeSetの使用方法とその実装を実践的な例を通して学びます。

2. コレクションをフィルタリングする

filtering a collectionの簡単な例から始めましょう。 ライブラリによって提供され、Predicatesユーティリティクラスを介して構築された、すぐに使用できる述語を使用します。

@Test
public void whenFilterWithIterables_thenFiltered() {
    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Iterable result
      = Iterables.filter(names, Predicates.containsPattern("a"));

    assertThat(result, containsInAnyOrder("Jane", "Adam"));
}

ご覧のとおり、名前のListをフィルタリングして、文字「a」を含む名前のみを取得しています。これには、Iterables.filter()を使用しています。

または、Collections2.filter()APIをうまく利用することもできます。

@Test
public void whenFilterWithCollections2_thenFiltered() {
    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result
      = Collections2.filter(names, Predicates.containsPattern("a"));

    assertEquals(2, result.size());
    assertThat(result, containsInAnyOrder("Jane", "Adam"));

    result.add("anna");
    assertEquals(5, names.size());
}

ここで注意すべき点がいくつかあります。まず、Collections.filter()の出力はa live view of the original collectionです。一方への変更は、もう一方に反映されます。

ここで、the result is constrained by the predicateを理解することも重要です。Predicateを満たさない要素を追加すると、IllegalArgumentExceptionがスローされます。

@Test(expected = IllegalArgumentException.class)
public void givenFilteredCollection_whenAddingInvalidElement_thenException() {
    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result
      = Collections2.filter(names, Predicates.containsPattern("a"));

    result.add("elvis");
}

3. カスタムフィルターPredicateを書き込む

次へ–ライブラリが提供するものを使用する代わりに、独自のPredicateを記述しましょう。 次の例では、「A」または「J」で始まる名前のみを取得する述語を定義します。

@Test
public void whenFilterCollectionWithCustomPredicate_thenFiltered() {
    Predicate predicate = new Predicate() {
        @Override
        public boolean apply(String input) {
            return input.startsWith("A") || input.startsWith("J");
        }
    };

    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result = Collections2.filter(names, predicate);

    assertEquals(3, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Adam"));
}

4. 複数の述語を組み合わせる

Predicates.or()Predicates.and()を使用して複数の述語を組み合わせることができます。 次の例では、名前のListをフィルタリングして、「J」で始まる名前または「a」を含まない名前を取得します。

@Test
public void whenFilterUsingMultiplePredicates_thenFiltered() {
    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result = Collections2.filter(names,
      Predicates.or(Predicates.containsPattern("J"),
      Predicates.not(Predicates.containsPattern("a"))));

    assertEquals(3, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Tom"));
}

5. コレクションのフィルタリング中にNULL値を削除する

次の例のように、コレクションをPredicates.notNull()でフィルタリングすることにより、コレクションからnull値をクリーンアップできます。

@Test
public void whenRemoveNullFromCollection_thenRemoved() {
    List names =
      Lists.newArrayList("John", null, "Jane", null, "Adam", "Tom");
    Collection result =
      Collections2.filter(names, Predicates.notNull());

    assertEquals(4, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Adam", "Tom"));
}

6. コレクション内のすべての要素が条件に一致するかどうかを確認します

次に、コレクション内のすべての要素が特定の条件に一致するかどうかを確認しましょう。 Iterables.all()を使用して、すべての名前に「n」または「m」が含まれているかどうかを確認してから、すべての要素に「a」が含まれているかどうかを確認します。

@Test
public void whenCheckingIfAllElementsMatchACondition_thenCorrect() {
    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");

    boolean result = Iterables.all(names, Predicates.containsPattern("n|m"));
    assertTrue(result);

    result = Iterables.all(names, Predicates.containsPattern("a"));
    assertFalse(result);
}

7. コレクションを変換する

では、transform a collection using a Guava Functionの方法を見てみましょう。 次の例では、Listの名前をIterables.transform()ListIntegers(名前の長さ)に変換します。

@Test
public void whenTransformWithIterables_thenTransformed() {
    Function function = new Function() {
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Iterable result = Iterables.transform(names, function);

    assertThat(result, contains(4, 4, 4, 3));
}

次の例のように、Collections2.transform()APIを使用することもできます。

@Test
public void whenTransformWithCollections2_thenTransformed() {
    Function func = new Function(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    List names =
      Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result = Collections2.transform(names, func);

    assertEquals(4, result.size());
    assertThat(result, contains(4, 4, 4, 3));

    result.remove(3);
    assertEquals(3, names.size());
}

Collections.transform()の出力はa live view of the original Collectionであることに注意してください–一方への変更は他方に影響します。

そして、以前と同じように、出力Collectionに要素を追加しようとすると、UnsupportedOperationExceptionがスローされます。

8. PredicateからFunctionを作成します

Functions.fromPredicate()を使用してPredicateからFunctionを作成することもできます。 もちろん、これは、述語の条件に従って、入力をBooleanに変換する関数になります。

次の例では、名前のListをブール値のリストに変換します。ここで、名前に「m」が含まれている場合、各要素は次のように表します。

@Test
public void whenCreatingAFunctionFromAPredicate_thenCorrect() {
    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result =
      Collections2.transform(names,
      Functions.forPredicate(Predicates.containsPattern("m")));

    assertEquals(4, result.size());
    assertThat(result, contains(false, false, true, true));
}

9. 2つの機能の構成

次へ–合成されたFunctionを使用してコレクションを変換する方法を見てみましょう。

Functions.compose()は、最初のFunctionの出力に2番目のFunctionを適用するため、2つの関数の合成を返します。

次の例では、最初のFunctionが名前をその長さに変換し、次に2番目のFunctionが長さをboolean値に変換します。これは、名前の長さが偶数かどうかを表します。

@Test
public void whenTransformingUsingComposedFunction_thenTransformed() {
    Function f1 = new Function(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    Function f2 = new Function(){
        @Override
        public Boolean apply(Integer input) {
            return input % 2 == 0;
        }
    };

    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result =
      Collections2.transform(names, Functions.compose(f2, f1));

    assertEquals(4, result.size());
    assertThat(result, contains(true, true, true, false));
}

10. フィルタリングと変換を組み合わせる

そして今、Guavaが持っているもう1つのクールなAPIを見てみましょう。これにより、フィルタリングと変換をチェーンでつなぐことができます。FluentIterableです。

次の例では、名前のListをフィルタリングしてから、FluentIterableを使用して変換します。

@Test
public void whenFilteringAndTransformingCollection_thenCorrect() {
    Predicate predicate = new Predicate() {
        @Override
        public boolean apply(String input) {
            return input.startsWith("A") || input.startsWith("T");
        }
    };

    Function func = new Function(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    List names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection result = FluentIterable.from(names)
                                               .filter(predicate)
                                               .transform(func)
                                               .toList();

    assertEquals(2, result.size());
    assertThat(result, containsInAnyOrder(4, 3));
}

場合によっては、命令型バージョンの方が読みやすく、機能的なアプローチよりも優先されることに注意してください。

11. 結論

最後に、Guavaを使用してコレクションをフィルタリングおよび変換する方法を学びました。 フィルタリングにはCollections2.filter()およびIterables.filter() APIを使用し、コレクションの変換にはCollections2.transform()およびIterables.transform()を使用しました。

最後に、フィルタリングと変換の両方を組み合わせる非常に興味深いFluentIterableの流暢なAPIを簡単に見てみました。

これらすべての例とコードスニペットcan be found in the GitHub projectの実装–これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。