Qual è il modo migliore per filtrare una raccolta Java?

Voglio filtrare un java.util.Collection basato su un predicato.

Java 8 ( 2014 ) risolve questo problema utilizzando stream e lambda in una riga di codice:

 List beerDrinkers = persons.stream() .filter(p -> p.getAge() > 16).collect(Collectors.toList()); 

Ecco un tutorial .

Usa Collection#removeIf per modificare la raccolta sul posto. (Nota: in questo caso, il predicato rimuoverà gli oggetti che soddisfano il predicato):

 persons.removeIf(p -> p.getAge() < = 16); 

lambdaj consente il filtraggio di raccolte senza loop di scrittura o classi interne:

 List beerDrinkers = select(persons, having(on(Person.class).getAge(), greaterThan(16))); 

Riesci a immaginare qualcosa di più leggibile?

Disclaimer: sono un contributore su lambdaj

Supponendo che tu stia usando Java 1.5 e che non puoi aggiungere Google Collections , farei qualcosa di molto simile a quello che hanno fatto i ragazzi di Google. Questa è una leggera variazione sui commenti di Jon.

Per prima cosa aggiungi questa interfaccia al tuo codice base.

 public interface IPredicate { boolean apply(T type); } 

I suoi implementatori possono rispondere quando un determinato predicato è vero per un certo tipo. Ad esempio, se T era User e AuthorizedUserPredicate implementa IPredicate , quindi AuthorizedUserPredicate#apply restituzione se l’ User passato è autorizzato.

Quindi in alcune classi di utilità, si potrebbe dire

 public static  Collection filter(Collection target, IPredicate predicate) { Collection result = new ArrayList(); for (T element: target) { if (predicate.apply(element)) { result.add(element); } } return result; } 

Quindi, supponendo che tu abbia l’uso di quanto sopra potrebbe essere

 Predicate isAuthorized = new Predicate() { public boolean apply(User user) { // binds a boolean method in User to a reference return user.isAuthorized(); } }; // allUsers is a Collection Collection authorizedUsers = filter(allUsers, isAuthorized); 

Se le prestazioni del controllo lineare sono preoccupanti, potrei desiderare di avere un object dominio che abbia la raccolta di destinazione. L’object dominio che ha la raccolta di destinazione avrebbe una logica di filtraggio per i metodi che inizializzano, aggiungono e impostano la raccolta di destinazione.

AGGIORNARE:

Nella class di utilità (diciamo Predicato), ho aggiunto un metodo select con un’opzione per il valore predefinito quando il predicato non restituisce il valore previsto e anche una proprietà statica per i parametri da utilizzare all’interno del nuovo IPredicate.

 public class Predicate { public static Object predicateParams; public static  Collection filter(Collection target, IPredicate predicate) { Collection result = new ArrayList(); for (T element : target) { if (predicate.apply(element)) { result.add(element); } } return result; } public static  T select(Collection target, IPredicate predicate) { T result = null; for (T element : target) { if (!predicate.apply(element)) continue; result = element; break; } return result; } public static  T select(Collection target, IPredicate predicate, T defaultValue) { T result = defaultValue; for (T element : target) { if (!predicate.apply(element)) continue; result = element; break; } return result; } } 

L’esempio seguente cerca gli oggetti mancanti tra le raccolte:

 List missingObjects = (List) Predicate.filter(myCollectionOfA, new IPredicate() { public boolean apply(MyTypeA objectOfA) { Predicate.predicateParams = objectOfA.getName(); return Predicate.select(myCollectionB, new IPredicate() { public boolean apply(MyTypeB objectOfB) { return objectOfB.getName().equals(Predicate.predicateParams.toString()); } }) == null; } }); 

Nell’esempio seguente, cerca un’istanza in una raccolta e restituisce il primo elemento della raccolta come valore predefinito quando l’istanza non viene trovata:

 MyType myObject = Predicate.select(collectionOfMyType, new IPredicate() { public boolean apply(MyType objectOfMyType) { return objectOfMyType.isDefault(); }}, collectionOfMyType.get(0)); 

AGGIORNAMENTO (dopo la versione Java 8):

Sono passati diversi anni da quando io (Alan) ho postato questa risposta, e non riesco ancora a credere che sto raccogliendo punti SO per questa risposta. Ad ogni modo, ora che Java 8 ha introdotto chiusure nella lingua, la mia risposta ora sarebbe considerevolmente diversa e più semplice. Con Java 8, non è necessaria una class di utilità statica distinta. Quindi, se vuoi trovare il primo elemento che corrisponde al tuo predicato.

 final UserService userService = ... // perhaps injected IoC final Optional userOption = userCollection.stream().filter(u -> { boolean isAuthorized = userService.isAuthorized(u); return isAuthorized; }).findFirst(); 

L’API JDK 8 per optionals ha la possibilità di get() , isPresent() , orElse(defaultUser) , orElseGet(userSupplier) e orElseThrow(exceptionSupplier) , così come altre funzioni ‘monadiche’ come map , flatMap e filter .

Se si desidera semplicemente raccogliere tutti gli utenti che corrispondono al predicato, quindi utilizzare i servizi di Collectors per terminare il stream nella raccolta desiderata.

 final UserService userService = ... // perhaps injected IoC final List userOption = userCollection.stream().filter(u -> { boolean isAuthorized = userService.isAuthorized(u); return isAuthorized; }).collect(Collectors.toList()); 

Vedi qui per ulteriori esempi su come funzionano gli stream di Java 8.

Usa CollectionUtils.filter (Collection, Predicate) , da Apache Commons.

Il modo “migliore” è una richiesta troppo ampia. È “più breve”? “Più veloce”? “Leggibile”? Filtrare sul posto o in un’altra raccolta?

Il modo più semplice (ma non più leggibile) è quello di iterarlo e utilizzare il metodo Iterator.remove ():

 Iterator it = col.iterator(); while( it.hasNext() ) { Foo foo = it.next(); if( !condition(foo) ) it.remove(); } 

Ora, per renderlo più leggibile, puoi inserirlo in un metodo di utilità. Quindi crea un’interfaccia IPredicate, crea un’implementazione anonima di tale interfaccia e fai qualcosa come:

 CollectionUtils.filterInPlace(col, new IPredicate(){ public boolean keepIt(Foo foo) { return foo.isBar(); } }); 

dove filterInPlace () itera la raccolta e chiama Predicate.keepIt () per sapere se l’istanza deve essere conservata nella raccolta.

Non vedo davvero una giustificazione per introdurre una libreria di terze parti solo per questo compito.

Prendi in considerazione Google Collections per un framework Collections aggiornato che supporti i generici.

AGGIORNAMENTO : la libreria delle raccolte di google è ora obsoleta. Dovresti invece usare l’ultima versione di Guava . Ha ancora tutte le stesse estensioni del framework delle collezioni, incluso un meccanismo per il filtraggio basato su un predicato.

Aspetta Java 8:

 List olderThan30 = //Create a Stream from the personList personList.stream(). //filter the element to select only those with age >= 30 filter(p -> p.age >= 30). //put those filtered elements into a new List. collect(Collectors.toList()); 

Dalla prima versione di Java 8, è ansible provare qualcosa come:

 Collection collection = ...; Stream stream = collection.stream().filter(...); 

Ad esempio, se si dispone di un elenco di numeri interi e si desidera filtrare i numeri che sono> 10 e quindi stampare tali numeri sulla console, è ansible fare qualcosa come:

 List numbers = Arrays.asList(12, 74, 5, 8, 16); numbers.stream().filter(n -> n > 10).forEach(System.out::println); 

Lancio RxJava sul ring, che è disponibile anche su Android . RxJava potrebbe non essere sempre l’opzione migliore, ma ti darà più flessibilità se desideri aggiungere più trasformazioni alla tua collezione o gestire gli errori durante il filtraggio.

 Observable.from(Arrays.asList(1, 2, 3, 4, 5)) .filter(new Func1() { public Boolean call(Integer i) { return i % 2 != 0; } }) .subscribe(new Action1() { public void call(Integer i) { System.out.println(i); } }); 

Produzione:

 1 3 5 

Maggiori dettagli sul filter di RxJava sono disponibili qui .

Il set up:

 public interface Predicate { public boolean filter(T t); } void filterCollection(Collection col, Predicate predicate) { for (Iterator i = col.iterator(); i.hasNext();) { T obj = i.next(); if (predicate.filter(obj)) { i.remove(); } } } 

L’utilizzo:

 List myList = ...; filterCollection(myList, new Predicate() { public boolean filter(MyObject obj) { return obj.shouldFilter(); } }); 

Che ne dici di un Java semplice e diretto

  List list ...; List newList = new ArrayList<>(); for (Customer c : list){ if (c.getName().equals("dd")) newList.add(c); } 

Semplice, leggibile e facile (e funziona su Android!) Ma se stai usando Java 8 puoi farlo in una linea dolce:

 List newList = list.stream().filter(c -> c.getName().equals("dd")).collect(toList()); 

Notare che toList () è importato staticamente

Sei sicuro di voler filtrare la raccolta stessa, piuttosto che un iteratore?

vedi org.apache.commons.collections.iterators.FilterIterator

o usando la versione 4 di apache commons org.apache.commons.collections4.iterators.FilterIterator

Diamo un’occhiata a come filtrare un elenco JDK e una MutableList incorporati utilizzando le raccolte Eclipse (precedentemente GS Collections ).

 List jdkList = Arrays.asList(1, 2, 3, 4, 5); MutableList ecList = Lists.mutable.with(1, 2, 3, 4, 5); 

Se si desidera filtrare i numeri inferiori a 3, ci si aspetta i seguenti output.

 List selected = Lists.mutable.with(1, 2); List rejected = Lists.mutable.with(3, 4, 5); 

Ecco come puoi filtrare usando una class interna anonima come Predicate .

 Predicate lessThan3 = new Predicate() { public boolean accept(Integer each) { return each < 3; } }; Assert.assertEquals(selected, Iterate.select(jdkList, lessThan3)); Assert.assertEquals(selected, ecList.select(lessThan3)); 

Ecco alcune alternative al filtraggio degli elenchi JDK e delle raccolte di Eclipse Collections che utilizzano la fabbrica Predicati .

 Assert.assertEquals(selected, Iterate.select(jdkList, Predicates.lessThan(3))); Assert.assertEquals(selected, ecList.select(Predicates.lessThan(3))); 

Ecco una versione che non assegna un object per il predicato, usando invece la factory Predicates2 con il metodo selectWith che prende un Predicate2 .

 Assert.assertEquals( selected, ecList.selectWith(Predicates2.lessThan(), 3)); 

A volte vuoi filtrare su una condizione negativa. Esiste un metodo speciale in Eclipse Collections per quello chiamato reject .

 Assert.assertEquals(rejected, Iterate.reject(jdkList, lessThan3)); Assert.assertEquals(rejected, ecList.reject(lessThan3)); 

Ecco come puoi filtrare usando un lambda Java 8 come Predicate .

 Assert.assertEquals(selected, Iterate.select(jdkList, each -> each < 3)); Assert.assertEquals(rejected, Iterate.reject(jdkList, each -> each < 3)); Assert.assertEquals(selected, gscList.select(each -> each < 3)); Assert.assertEquals(rejected, gscList.reject(each -> each < 3)); 

La partition metodo restituirà due raccolte, contenenti gli elementi selezionati e rifiutati dal Predicate .

 PartitionIterable jdkPartitioned = Iterate.partition(jdkList, lessThan3); Assert.assertEquals(selected, jdkPartitioned.getSelected()); Assert.assertEquals(rejected, jdkPartitioned.getRejected()); PartitionList ecPartitioned = gscList.partition(lessThan3); Assert.assertEquals(selected, ecPartitioned.getSelected()); Assert.assertEquals(rejected, ecPartitioned.getRejected()); 

Nota: sono un committer per le raccolte di Eclipse.

Con la DSL di ForEach puoi scrivere

 import static ch.akuhn.util.query.Query.select; import static ch.akuhn.util.query.Query.$result; import ch.akuhn.util.query.Select; Collection collection = ... for (Select each : select(collection)) { each.yield = each.value.length() > 3; } Collection result = $result(); 

Data una collezione di [The, quick, brown, fox, jumps, over, the, lazy, dog] questo si traduce in [quick, brown, jumps, over, pigro], cioè tutte le stringhe più lunghe di tre caratteri.

Lo sono tutti gli stili di iterazione supportati da ForEach DSL

  • AllSatisfy
  • AnySatisfy
  • Collect
  • Counnt
  • CutPieces
  • Detect
  • GroupedBy
  • IndexOf
  • InjectInto
  • Reject
  • Select

Per maggiori dettagli, consultare https://www.iam.unibe.ch/scg/svn_repos/Sources/ForEach

Il metodo Collections2.filter (Collection, Predicate) nella libreria Guava di Google fa esattamente quello che stai cercando.

Questo, combinato con la mancanza di chiusure reali, è il mio più grande cruccio per Java. Onestamente, la maggior parte dei metodi sopra menzionati sono abbastanza facili da leggere e REALMENTE efficienti; tuttavia, dopo aver trascorso del tempo con .Net, Erlang, ecc … la comprensione delle liste integrata a livello di lingua rende tutto molto più pulito. Senza aggiunte a livello di linguaggio, Java non può essere pulito come molte altre lingue in quest’area.

Se le prestazioni sono un problema enorme, le raccolte Google sono la soluzione giusta (o scrivi la tua semplice utilità per i predicati). La syntax Lambdaj è più leggibile per alcune persone, ma non è altrettanto efficiente.

E poi c’è una biblioteca che ho scritto. Ignorerò qualsiasi domanda in merito alla sua efficienza (sì, è così male) …… Sì, so che si basa chiaramente sulla riflessione, e no in realtà non la uso, ma funziona:

 LinkedList list = ...... LinkedList filtered = Query.from(list).where(Condition.ensure("age", Op.GTE, 21)); 

O

 LinkedList list = .... LinkedList filtered = Query.from(list).where("x => x.age >= 21"); 

JFilter http://code.google.com/p/jfilter/ è più adatto alle tue esigenze.

JFilter è una libreria open source semplice e ad alte prestazioni per interrogare la raccolta di bean Java.

Caratteristiche chiave

  • Supporto delle proprietà collection (java.util.Collection, java.util.Map e Array).
  • Supporto della raccolta all’interno della raccolta di qualsiasi profondità.
  • Supporto di query interne.
  • Supporto di query parametrizzate.
  • Può filtrare 1 milione di record in pochi 100 ms.
  • Il filtro (query) viene fornito in semplice formato json, è come le query Mangodb. Di seguito sono riportati alcuni esempi.
  • {“id”: {“$ le”: “10”}
    • dove la proprietà ID dell’object è inferiore a 10.
  • {“id”: {“$ in”: [“0”, “100”]}}
    • dove la proprietà id dell’object è 0 o 100.
  • { “LineItems”: { “lineAmount”: “1”}}
    • dove proprietà lineItems collection di tipo parametrizzato ha lineAmount è uguale a 1.
  • {“$ and”: [{“id”: “0”}, {“indirizzoBilling”: {“città”: “DEL”}}]}
    • dove la proprietà id è 0 e la proprietà billingAddress.city è DEL.
  • {“lineItems”: {“tasse”: {“chiave”: {“codice”: “GST”}, “valore”: {“$ gt”: “1.01”}}}}
    • dove la proprietà della collezione lineItems di tipo parametrizzato che ha la proprietà del tipo di mappa delle tasse del tipo parameteriszed ha il codice uguale al valore GST maggiore di 1.01.
  • {‘$ o’: [{‘code’: ’10’}, {‘skus’: {‘$ and’: [{‘price’: {‘$ in’: [’20’, ’40’]} }, {‘code’: ‘RedApple’}]}}}}
    • Seleziona tutti i prodotti in cui il codice prodotto è 10 o il prezzo sku in 20 e 40 e il codice sku è “RedApple”.

Ho scritto una class Iterable estesa che supporta l’applicazione di algoritmi funzionali senza copiare il contenuto della raccolta.

Uso:

 List myList = new ArrayList(){ 1, 2, 3, 4, 5 } Iterable filtered = Iterable.wrap(myList).select(new Predicate1() { public Boolean call(Integer n) throws FunctionalException { return n % 2 == 0; } }) for( int n : filtered ) { System.out.println(n); } 

Il codice sopra verrà effettivamente eseguito

 for( int n : myList ) { if( n % 2 == 0 ) { System.out.println(n); } } 

Utilizzare il motore di query di raccolta (CQEngine) . È di gran lunga il modo più veloce per farlo.

Vedi anche: Come si interrogano le raccolte di oggetti in Java (Criteria / SQL-like)?

Poiché java 9 Collectors.filtering è abilitato:

 public static  Collector filtering(Predicate< ? super T> predicate, Collector< ? super T, A, R> downstream) 

Quindi il filtraggio dovrebbe essere:

 collection.stream().collect(Collectors.filtering(predicate, collector)) 

Esempio:

 List oddNumbers = List.of(1, 19, 15, 10, -10).stream() .collect(Collectors.filtering(i -> i % 2 == 1, Collectors.toList())); 

La semplice soluzione pre-Java8:

 ArrayList filtered = new ArrayList(); for (Item item : items) if (condition(item)) filtered.add(item); 

Sfortunatamente questa soluzione non è completamente generica, ma genera un elenco piuttosto che il tipo della raccolta data. Inoltre, introdurre librerie o scrivere funzioni che avvolgono questo codice mi sembra eccessivo, a meno che la condizione non sia complessa, ma in tal caso è ansible scrivere una funzione per la condizione.

https://code.google.com/p/joquery/

Supporta diverse possibilità,

Data la raccolta,

 Collection testList = new ArrayList<>(); 

di tipo,

 class Dto { private int id; private String text; public int getId() { return id; } public int getText() { return text; } } 

Filtro

Java 7

 Filter query = CQ.filter(testList) .where() .property("id").eq().value(1); Collection filtered = query.list(); 

Java 8

 Filter query = CQ.filter(testList) .where() .property(Dto::getId) .eq().value(1); Collection filtered = query.list(); 

Anche,

 Filter query = CQ.filter() .from(testList) .where() .property(Dto::getId).between().value(1).value(2) .and() .property(Dto::grtText).in().value(new string[]{"a","b"}); 

Ordinamento (disponibile anche per Java 7)

 Filter query = CQ.filter(testList) .orderBy() .property(Dto::getId) .property(Dto::getName) Collection sorted = query.list(); 

Raggruppamento (disponibile anche per Java 7)

 GroupQuery query = CQ.query(testList) .group() .groupBy(Dto::getId) Collection> grouped = query.list(); 

Join (disponibile anche per Java 7)

Dato,

 class LeftDto { private int id; private String text; public int getId() { return id; } public int getText() { return text; } } class RightDto { private int id; private int leftId; private String text; public int getId() { return id; } public int getLeftId() { return leftId; } public int getText() { return text; } } class JoinedDto { private int leftId; private int rightId; private String text; public JoinedDto(int leftId,int rightId,String text) { this.leftId = leftId; this.rightId = rightId; this.text = text; } public int getLeftId() { return leftId; } public int getRightId() { return rightId; } public int getText() { return text; } } Collection leftList = new ArrayList<>(); Collection rightList = new ArrayList<>(); 

Può essere unito come,

 Collection results = CQ.query().from(leftList) .innerJoin(CQ.query().from(rightList)) .on(LeftFyo::getId, RightDto::getLeftId) .transformDirect(selection -> new JoinedDto(selection.getLeft().getText() , selection.getLeft().getId() , selection.getRight().getId()) ) .list(); 

espressioni

 Filter query = CQ.filter() .from(testList) .where() .exec(s -> s.getId() + 1).eq().value(2); 

La mia risposta si basa su quella di Kevin Wong, qui come one-liner utilizzando CollectionUtils dalla spring e un’espressione lambda Java 8.

 CollectionUtils.filter(list, p -> ((Person) p).getAge() > 16); 

Questo è tanto conciso e leggibile come qualsiasi altra alternativa che ho visto (senza usare librerie basate sull’aspetto)

Spring CollectionUtils è disponibile dalla versione spring 4.0.2.RELEASE e ricorda che è necessario JDK 1.8 e il livello di lingua 8+.

Alcune grandi risposte davvero grandi qui. Io, mi piacerebbe tenerlo il più semplice e leggibile ansible:

 public abstract class AbstractFilter { /** * Method that returns whether an item is to be included or not. * @param item an item from the given collection. * @return true if this item is to be included in the collection, false in case it has to be removed. */ protected abstract boolean excludeItem(T item); public void filter(Collection collection) { if (CollectionUtils.isNotEmpty(collection)) { Iterator iterator = collection.iterator(); while (iterator.hasNext()) { if (excludeItem(iterator.next())) { iterator.remove(); } } } } } 

Usando java 8 , in particolare lambda expression , puoi farlo semplicemente come nell’esempio seguente:

 myProducts.stream().filter(prod -> prod.price>10).collect(Collectors.toList()) 

dove per ogni product all’interno myProducts collezione myProducts , se prod.price>10 , aggiungere questo prodotto alla nuova lista filtrata.

Con Guava:

 Collection collection = Lists.newArrayList(1, 2, 3, 4, 5); Iterators.removeIf(collection.iterator(), new Predicate() { @Override public boolean apply(Integer i) { return i % 2 == 0; } }); System.out.println(collection); // Prints 1, 3, 5 

Avevo bisogno di filtrare una lista a seconda dei valori già presenti nella lista. Ad esempio, rimuovere tutti i valori seguenti è inferiore al valore corrente. {2 5 3 4 7 5} -> {2 5 7}. O per esempio per rimuovere tutti i duplicati {3 5 4 2 3 5 6} -> {3 5 4 2 6}.

 public class Filter { public static  void List(List list, Chooser chooser) { List toBeRemoved = new ArrayList<>(); leftloop: for (int right = 1; right < list.size(); ++right) { for (int left = 0; left < right; ++left) { if (toBeRemoved.contains(left)) { continue; } Keep keep = chooser.choose(list.get(left), list.get(right)); switch (keep) { case LEFT: toBeRemoved.add(right); continue leftloop; case RIGHT: toBeRemoved.add(left); break; case NONE: toBeRemoved.add(left); toBeRemoved.add(right); continue leftloop; } } } Collections.sort(toBeRemoved, new Comparator() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); for (int i : toBeRemoved) { if (i >= 0 && i < list.size()) { list.remove(i); } } } public static  void List(List list, Keeper keeper) { Iterator iterator = list.iterator(); while (iterator.hasNext()) { if (!keeper.keep(iterator.next())) { iterator.remove(); } } } public interface Keeper { boolean keep(E obj); } public interface Chooser { Keep choose(E left, E right); } public enum Keep { LEFT, RIGHT, BOTH, NONE; } } 

Questo sarà usato in questo modo.

 List names = new ArrayList<>(); names.add("Anders"); names.add("Stefan"); names.add("Anders"); Filter.List(names, new Filter.Chooser() { @Override public Filter.Keep choose(String left, String right) { return left.equals(right) ? Filter.Keep.LEFT : Filter.Keep.BOTH; } });