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 ?

Foi útil?

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);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top