Есть ли элегантный способ удаления нулей при преобразовании коллекции с помощью Guava?
У меня есть вопрос об упрощении кода обработки коллекции при использовании Google Collections (обновление: Guava).
У меня есть куча объектов "Компьютер", и я хочу получить коллекцию своих "идентификаторов ресурсов". Это делается следующим образом:
Collection<Computer> matchingComputers = findComputers();
Collection<String> resourceIds =
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
}));
Теперь getResourceId()
может возвращать значение null (и изменение этого параметра сейчас не является), но в этом случае я бы хотел опустить нули из результирующей коллекции String.
Здесь один из способов фильтрации нулей:
Collections2.filter(resourceIds, new Predicate<String>() {
@Override
public boolean apply(String input) {
return input != null;
}
});
Вы можете соединить все это так:
Collection<String> resourceIds = Collections2.filter(
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
})), new Predicate<String>() {
@Override
public boolean apply(String input) {
return input != null;
}
});
Но это едва ли изящно, не говоря уже о читаемости, для такой простой задачи! На самом деле простой старый Java-код (без каких-либо причудливых свойств Predicate или Function вообще), возможно, будет намного чище:
Collection<String> resourceIds = Lists.newArrayList();
for (Computer computer : matchingComputers) {
String resourceId = computer.getResourceId();
if (resourceId != null) {
resourceIds.add(resourceId);
}
}
Использование вышеизложенного, безусловно, также является вариантом, но из любопытства (и желание узнать больше о Коллекциях Google), , вы можете сделать то же самое более коротким или более элегантным способом с помощью Коллекций Google?
Ответы
Ответ 1
Там уже есть предикат в Predicates
, который поможет вам здесь - Predicates.notNull()
- и вы можете использовать Iterables.filter()
и тот факт, что Lists.newArrayList()
может взять Iterable
, чтобы очистить это немного больше.
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, yourFunction),
Predicates.notNull()
)
);
Если вам действительно не нужен Collection
, просто Iterable
, тогда вызов Lists.newArrayList()
может исчезнуть, и вы снова сделаете очиститель заново!
Я подозреваю, что вы можете обнаружить, что Function
снова пригодится и будет наиболее полезным объявлено как
public class Computer {
// ...
public static Function<Computer, String> TO_ID = ...;
}
который еще больше очищает его (и будет способствовать повторному использованию).
Ответ 2
Немного "более симпатичный" синтаксис с FluentIterable
(так как Guava 12):
ImmutableList<String> resourceIds = FluentIterable.from(matchingComputers)
.transform(getResourceId)
.filter(Predicates.notNull())
.toList();
static final Function<Computer, String> getResourceId =
new Function<Computer, String>() {
@Override
public String apply(Computer computer) {
return computer.getResourceId();
}
};
Обратите внимание, что возвращенный список - это ImmutableList
. Однако вы можете использовать метод copyInto()
, чтобы вылить элементы в произвольную коллекцию.
Ответ 3
Потребовалось больше времени @Jon Skeet, но потоки Java 8 делают это простым:
List<String> resourceIds = computers.stream()
.map(Computer::getResourceId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
Вы также можете использовать .filter(x -> x != null)
, если хотите; разница очень незначительна.
Ответ 4
Во-первых, я бы где-то создал постоянный фильтр:
public static final Predicate<Object> NULL_FILTER = new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return input != null;
}
}
Затем вы можете использовать:
Iterable<String> ids = Iterables.transform(matchingComputers,
new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
}));
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(ids, NULL_FILTER));
В коде можно использовать один и тот же нулевой фильтр.
Если вы используете одну и ту же вычислительную функцию в другом месте, вы также можете сделать это константой, оставив только:
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, RESOURCE_ID_PROJECTION),
NULL_FILTER));
Это, конечно, не так хорошо, как эквивалент С#, но все это будет намного лучше в Java 7 с помощью методов закрытия и расширения:)
Ответ 5
Вы можете написать свой собственный метод. это отфильтрует нули для любой функции, которая возвращает null из метода apply.
public static <F, T> Collection<T> transformAndFilterNulls(List<F> fromList, Function<? super F, ? extends T> function) {
return Collections2.filter(Lists.transform(fromList, function), Predicates.<T>notNull());
}
Затем метод можно вызвать со следующим кодом.
Collection c = transformAndFilterNulls(Lists.newArrayList("", "SD", "DDF"), new Function<String, Long>() {
@Override
public Long apply(String s) {
return s.isEmpty() ? 20L : null;
}
});
System.err.println(c);