Esiste un modo elegante per rimuovere i null mentre trasforma una collezione usando Guava?
-
05-07-2019 - |
Domanda
Ho una domanda sulla semplificazione del codice di gestione delle raccolte quando utilizzo Google Collections ( aggiorna : Guava ).
Ho un sacco di " Computer " oggetti e voglio finire con una raccolta dei loro "ID risorsa". Questo è fatto in questo modo:
Collection<Computer> matchingComputers = findComputers();
Collection<String> resourceIds =
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
}));
Ora, getResourceId ()
potrebbe restituire null (e cambiando che al momento non è un'opzione), ma in questo caso vorrei omettere i null dalla raccolta String risultante.
Ecco un modo per filtrare i null:
Collections2.filter(resourceIds, new Predicate<String>() {
@Override
public boolean apply(String input) {
return input != null;
}
});
Potresti mettere tutto insieme così:
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;
}
});
Ma questo non è certo elegante, figuriamoci leggibile, per un compito così semplice! In effetti, il semplice vecchio codice Java (senza alcun elaborato predicato o funzione) sarebbe probabilmente molto più pulito:
Collection<String> resourceIds = Lists.newArrayList();
for (Computer computer : matchingComputers) {
String resourceId = computer.getResourceId();
if (resourceId != null) {
resourceIds.add(resourceId);
}
}
L'uso di quanto sopra è certamente anche un'opzione, ma per curiosità (e desiderio di saperne di più su Google Collections), puoi fare esattamente la stessa cosa in un modo più breve o più elegante usando Google Collections ?
Soluzione
Esiste già un predicato in Predicati
che ti aiuteranno qui - Predicates.notNull ()
- e puoi usare Iterables.filter ()
e il fatto che Lists.newArrayList ()
può richiedere un Iterable
per ripulire un po 'di più.
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, yourFunction),
Predicates.notNull()
)
);
Se in realtà non hai bisogno di un Collection
, solo un Iterable
, anche la chiamata Lists.newArrayList ()
può sparire e sei di nuovo più pulito!
Sospetto che potresti scoprire che la Funzione
tornerà utile e che sarà molto utile dichiarata come
public class Computer {
// ...
public static Function<Computer, String> TO_ID = ...;
}
che risolve ulteriormente questo problema (e promuoverà il riutilizzo).
Altri suggerimenti
Un po '"più bello" sintassi con FluentIterable
(da 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();
}
};
Si noti che l'elenco restituito è un ImmutableList
. Tuttavia, puoi usare il metodo copyInto ()
per versare gli elementi in una raccolta arbitraria.
Ci è voluto più tempo di @Jon Skeet previsto , ma i flussi Java 8 lo rendono semplice:
List<String> resourceIds = computers.stream()
.map(Computer::getResourceId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
Puoi anche usare .filter (x - > x! = null)
se vuoi; la differenza è molto minore .
In primo luogo, creerei un filtro costante da qualche parte:
public static final Predicate<Object> NULL_FILTER = new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return input != null;
}
}
Quindi puoi usare:
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));
Puoi usare lo stesso filtro null ovunque nel tuo codice.
Se usi la stessa funzione di calcolo altrove, puoi renderla costante, lasciando solo:
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, RESOURCE_ID_PROJECTION),
NULL_FILTER));
Non è certo bello come l'equivalente in C #, ma tutto ciò diventerà lotto più bello in Java 7 con chiusure e metodi di estensione :)
Potresti scrivere il tuo metodo in questo modo. questo filtrerà i null per qualsiasi Funzione che restituisce null dal metodo 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());
}
Il metodo può quindi essere chiamato con il seguente codice.
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);