1. Обзор
В этом руководстве мы покажем, как фильтровать и преобразовывать коллекции с помощью Guava .
Мы будем фильтровать с помощью Predicates , преобразовывать с помощью функций, предоставляемых библиотекой, и, наконец, мы увидим, как сочетать фильтрацию и преобразование.
2. Фильтровать коллекцию
Начнем с простого примера фильтрации коллекции . Мы будем использовать предикат из коробки, предоставленный библиотекой и созданный с помощью служебного класса Predicates :
@Test
public void whenFilterWithIterables_thenFiltered() {
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Iterable<String> result
= Iterables.filter(names, Predicates.containsPattern("a"));
assertThat(result, containsInAnyOrder("Jane", "Adam"));
}
Как видите, мы фильтруем список
имен, чтобы получить только те имена, которые содержат символ «а», и для этого мы используем Iterables.filter()
.
В качестве альтернативы мы также можем использовать API Collections2.filter() :
@Test
public void whenFilterWithCollections2_thenFiltered() {
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<String> 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<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<String> result
= Collections2.filter(names, Predicates.containsPattern("a"));
result.add("elvis");
}
3. Напишите пользовательский предикат фильтра
Далее — давайте напишем свой собственный предикат
вместо того, который предоставлен библиотекой. В следующем примере мы определим предикат, который получает только имена, начинающиеся с «A» или «J»:
@Test
public void whenFilterCollectionWithCustomPredicate_thenFiltered() {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.startsWith("A") || input.startsWith("J");
}
};
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<String> result = Collections2.filter(names, predicate);
assertEquals(3, result.size());
assertThat(result, containsInAnyOrder("John", "Jane", "Adam"));
}
4. Комбинируйте несколько предикатов
Мы можем комбинировать несколько предикатов, используя Predicates.or()
и Predicates.and()
.
В следующем примере мы фильтруем список
имен, чтобы получить имена, которые начинаются с «J» или не содержат «a»:
@Test
public void whenFilterUsingMultiplePredicates_thenFiltered() {
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<String> 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. Удалите нулевые значения при фильтрации коллекции
Мы можем очистить коллекцию от нулевых значений, отфильтровав ее с помощью
Predicates.notNull()
, как в следующем примере:
@Test
public void whenRemoveNullFromCollection_thenRemoved() {
List<String> names =
Lists.newArrayList("John", null, "Jane", null, "Adam", "Tom");
Collection<String> 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<String> 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<String, Integer> function = new Function<String, Integer>() {
@Override
public Integer apply(String input) {
return input.length();
}
};
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Iterable<Integer> result = Iterables.transform(names, function);
assertThat(result, contains(4, 4, 4, 3));
}
Мы также можем использовать API Collections2.transform()
, как в следующем примере:
@Test
public void whenTransformWithCollections2_thenTransformed() {
Function<String,Integer> func = new Function<String,Integer>(){
@Override
public Integer apply(String input) {
return input.length();
}
};
List<String> names =
Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<Integer> 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. Создать функцию
из предиката
Мы также можем создать функцию
из предиката
, используя Functions.fromPredicate()
. Это, конечно, будет функция, которая преобразует входные данные в Boolean
в соответствии с условием предиката.
В следующем примере мы преобразуем список
имен в список логических значений, где каждый элемент представляет, содержит ли имя «m»:
@Test
public void whenCreatingAFunctionFromAPredicate_thenCorrect() {
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<Boolean> 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<String,Integer> f1 = new Function<String,Integer>(){
@Override
public Integer apply(String input) {
return input.length();
}
};
Function<Integer,Boolean> f2 = new Function<Integer,Boolean>(){
@Override
public Boolean apply(Integer input) {
return input % 2 == 0;
}
};
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<Boolean> 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<String> predicate = new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.startsWith("A") || input.startsWith("T");
}
};
Function<String, Integer> func = new Function<String,Integer>(){
@Override
public Integer apply(String input) {
return input.length();
}
};
List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
Collection<Integer> result = FluentIterable.from(names)
.filter(predicate)
.transform(func)
.toList();
assertEquals(2, result.size());
assertThat(result, containsInAnyOrder(4, 3));
}
Стоит отметить, что в некоторых случаях императивная версия более читабельна и ее следует предпочесть функциональному подходу.
11. Заключение
Наконец, мы научились фильтровать и преобразовывать коллекции с помощью Guava. Мы использовали API Collections2.filter()
и Iterables.filter()
для фильтрации, а также Collections2.transform()
и Iterables.transform()
для преобразования коллекций.
Наконец, мы кратко рассмотрели очень интересный FluentIterable Fluent
API, сочетающий фильтрацию и преобразование.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub — это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.