Рубрики
Без рубрики

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

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

Автор оригинала: Eugen Paraschiv.

1. Обзор

В этом уроке мы проиллюстрируем, как фильтровать и преобразовывать коллекции с помощью Guava .

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

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

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

Путеводитель по Гуаве Multimap

Руководство по набору ассортимента Гуавы

2. Отфильтруйте коллекцию

Давайте начнем с простого примера фильтрации коллекции . Мы будем использовать готовый предикат, предоставленный библиотекой и созданный с помощью Предикатов служебного класса:

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

Как вы можете видеть, мы фильтруем Список имен, чтобы получить только имена, содержащие символ “а”, – и мы используем 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() является живым представлением исходной коллекции – изменения в одной будут отражены в другой.

Также важно понимать, что теперь результат ограничен предикатом – если мы добавим элемент , который не удовлетворяет этому Предикату , будет выдано исключение 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. Напишите Пользовательский Предикат Фильтра

Далее – давайте напишем наш собственный Предикат вместо использования предиката, предоставленного библиотекой. В следующем примере мы определим предикат, который получает только имена, начинающиеся с “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. Объедините Несколько Предикатов

Мы можем объединить несколько Предикатов, используя Предикаты.или() и Предикаты.и() .
В следующем примере – мы фильтруем Список
имен, чтобы получить имена, которые начинаются с “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 из коллекции, отфильтровав ее с помощью Предикатов.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. Преобразуйте коллекцию

Теперь – давайте посмотрим, как преобразовать коллекцию с помощью функции Guava |/. В следующем примере – мы преобразуем Список имен в Список Целых чисел (длина имени) с помощью 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));
}

Мы также можем использовать 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() является живым представлением оригинала Коллекции – изменения в одном влияют на другое.

И – как и раньше – если мы попытаемся добавить элемент в выходную Коллекцию , будет выдано исключение UnsupportedOperationException .

8. Создайте функцию из предиката

Мы также можем создать Функцию из Предиката , используя Функции.из предиката() . Это, конечно, будет функция , которая преобразует входные данные в Логическое значение в соответствии с условием предиката.

В следующем примере мы преобразуем Список имен в список логических значений, где каждый элемент представляет, содержит ли имя “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. Состав из двух функций

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

Functions.compose() возвращает состав двух функций при применении второй Функции на выходе первой Функции .

В следующем примере – первая Функция преобразует имя в его длину, затем вторая Функция преобразует длину в логическое значение, которое представляет, четна ли длина имени:

@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 .

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

Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub – это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.