Фильтрация и преобразование коллекций в Гуаве

Фильтрация и преобразование коллекций в Гуаве

1. обзор

В этом руководстве мы покажем, как использоватьfilter and transform collections with Guava.

Мы будем фильтровать с помощью предикатов, преобразовывать с помощью функций, которые предоставляет библиотека, и, наконец, мы увидим, как объединить фильтрацию и преобразование.

Дальнейшее чтение:

Новый поток, компаратор и коллектор в Гуаве 21

Краткое и практическое руководство по инструментам в пакете common.collect в Guava 21.

Read more

Руководство по Guava Multimap

Краткое руководство по Guava Multimap по сравнению со стандартным java.util.Map

Read more

Руководство по Guava RangeSet

Узнайте, как использовать Google Guava RangeSet и его реализации на практических примерах.

Read more

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().

В качестве альтернативы мы также можем эффективно использовать APICollections2.filter():

@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(), как в следующем примере:

@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 имен вList изIntegers (длина имени) с помощьюIterables.transform():

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

Мы также можем использовать APICollections2.transform(), как в следующем примере:

@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. СоздатьFunction изPredicate

Мы также можем создатьFunction изPredicate, используяFunctions.fromPredicate(). Это, конечно, будет функция, которая преобразует входные данные в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. Состав двух функций

Далее - давайте посмотрим, как преобразовать коллекцию с помощью составногоFunction.

Functions.compose() возвращает композицию двух функций, поскольку он применяет второйFunction к выходу первогоFunction.

В следующем примере - первыйFunction преобразует имя в его длину, затем второй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. Объедините фильтрацию и преобразование

А теперь - давайте посмотрим еще один классный API, который есть в Guava - тот, который фактически позволит нам объединить фильтрацию и преобразование вместе -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. Мы использовали APICollections2.filter() иIterables.filter() для фильтрации, а такжеCollections2.transform() иIterables.transform() для преобразования коллекций.

Наконец, мы кратко рассмотрели очень интересный APIFluentIterable fluent, сочетающий в себе фильтрацию и преобразование.

Реализация всех этих примеров и фрагментов кодаcan be found in the GitHub project - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.