Existe uma maneira elegante de remover nulos ao transformar uma coleção usando goiaba?
-
05-07-2019 - |
Pergunta
Eu tenho uma pergunta sobre a simplificação de alguns código de manipulação de coleção, quando se usa Google Collections ( Atualização : Goiaba ).
Eu tenho um monte de objetos "Computador", e eu quero acabar com uma coleção de seus s "ID do recurso". Isso é feito da seguinte forma:
Collection<Computer> matchingComputers = findComputers();
Collection<String> resourceIds =
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
}));
Agora, getResourceId()
pode retornar nulo (e mudar isso não é uma opção agora), mas neste caso eu gostaria de nulos omitir da coleção string resultante.
Aqui está uma maneira de nulos filtrar:
Collections2.filter(resourceIds, new Predicate<String>() {
@Override
public boolean apply(String input) {
return input != null;
}
});
Você poderia colocar tudo isso junto como esta:
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;
}
});
Mas este não é elegante, muito menos legível, para uma tarefa tão simples! Na verdade, o código Java velho liso (sem fantasia predicado ou coisas função em tudo) seria sem dúvida ser muito mais limpo:
Collection<String> resourceIds = Lists.newArrayList();
for (Computer computer : matchingComputers) {
String resourceId = computer.getResourceId();
if (resourceId != null) {
resourceIds.add(resourceId);
}
}
Usando o acima é certamente também uma opção, mas por curiosidade (e desejo de aprender mais do Google Collections), você pode fazer exatamente a mesma coisa, de alguma forma mais curto ou mais elegante usando o Google Collections ?
Solução
Já existe um predicado em Predicates
que irá ajudá-lo aqui - Predicates.notNull()
-. e você pode usar Iterables.filter()
eo fato de que Lists.newArrayList()
pode tomar um Iterable
para limpar isto um pouco mais
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, yourFunction),
Predicates.notNull()
)
);
Se você realmente não precisa de um Collection
, apenas uma Iterable
, então a chamada Lists.newArrayList()
pode ir longe demais e você está um passo mais limpo novamente!
Eu suspeito que você pode achar que o Function
virá a calhar novamente, e será mais útil declarado como
public class Computer {
// ...
public static Function<Computer, String> TO_ID = ...;
}
que limpa isto ainda mais (e vai promover a reutilização).
Outras dicas
Um pouco "mais bonita" sintaxe com FluentIterable
(desde Goiaba 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();
}
};
Note que a lista retornada é um ImmutableList
. No entanto, você pode usar o método copyInto()
a derramar os elementos em uma coleção arbitrária.
Demorou mais do que @ Jon Skeet esperado , mas Java 8 fluxos fazem este simples:
List<String> resourceIds = computers.stream()
.map(Computer::getResourceId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
Você também pode usar .filter(x -> x != null)
se você gosta; a diferença é muito menor .
Em primeiro lugar, eu criar um lugar filtro constante:
public static final Predicate<Object> NULL_FILTER = new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return input != null;
}
}
Em seguida, você pode usar:
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));
Você pode usar o mesmo filtro nulo em todos os lugares em seu código.
Se você usar a mesma função de computação em outro lugar, você pode fazer que uma constante também, deixando apenas:
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, RESOURCE_ID_PROJECTION),
NULL_FILTER));
Não é certamente tão bom quanto o C # equivalente seria, mas isso é tudo vai obter um muito mais agradável em Java 7 com encerramentos e métodos de extensão:)
Você pode escrever seu próprio método assim. isso irá filtrar nulos para qualquer função que retorna nulo a partir do método aplicado.
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());
}
O método pode então ser chamado com o seguinte código.
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);