Voglio filtrare un java.util.Collection
basato su un predicato.
Voglio filtrare un java.util.Collection
basato su un predicato.
Risposte:
Java 8 ( 2014 ) risolve questo problema usando stream e lambda in una riga di codice:
List<Person> beerDrinkers = persons.stream()
.filter(p -> p.getAge() > 16).collect(Collectors.toList());
Ecco un tutorial .
Utilizzare Collection#removeIf
per modificare la raccolta in atto. (Nota: in questo caso, il predicato rimuoverà gli oggetti che soddisfano il predicato):
persons.removeIf(p -> p.getAge() <= 16);
lambdaj consente di filtrare le raccolte senza scrivere loop o classi interne:
List<Person> beerDrinkers = select(persons, having(on(Person.class).getAge(),
greaterThan(16)));
Riesci a immaginare qualcosa di più leggibile?
Disclaimer: sono un collaboratore di lambdaj
persons.removeIf(p -> p.getAge() <= 16);
Supponendo che si stia utilizzando Java 1.5 e che non sia possibile aggiungere raccolte Google , 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 codebase.
public interface IPredicate<T> { boolean apply(T type); }
I suoi implementatori possono rispondere quando un determinato predicato è vero per un certo tipo. Ad esempio, se T
fosse User
e AuthorizedUserPredicate<User>
implementa IPredicate<T>
, quindi AuthorizedUserPredicate#apply
restituisce se il passatoUser
è autorizzato.
Quindi, in alcune classi di utilità, potresti dire
public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
Collection<T> result = new ArrayList<T>();
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<User> isAuthorized = new Predicate<User>() {
public boolean apply(User user) {
// binds a boolean method in User to a reference
return user.isAuthorized();
}
};
// allUsers is a Collection<User>
Collection<User> authorizedUsers = filter(allUsers, isAuthorized);
Se le prestazioni sul controllo lineare sono preoccupanti, allora potrei voler avere un oggetto di dominio con la raccolta di destinazione. L'oggetto dominio che ha la raccolta di destinazione avrebbe una logica di filtro per i metodi che inizializzano, aggiungono e impostano la raccolta di destinazione.
AGGIORNARE:
Nella classe 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 <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
Collection<T> result = new ArrayList<T>();
for (T element : target) {
if (predicate.apply(element)) {
result.add(element);
}
}
return result;
}
public static <T> T select(Collection<T> target, IPredicate<T> predicate) {
T result = null;
for (T element : target) {
if (!predicate.apply(element))
continue;
result = element;
break;
}
return result;
}
public static <T> T select(Collection<T> target, IPredicate<T> 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<MyTypeA> missingObjects = (List<MyTypeA>) Predicate.filter(myCollectionOfA,
new IPredicate<MyTypeA>() {
public boolean apply(MyTypeA objectOfA) {
Predicate.predicateParams = objectOfA.getName();
return Predicate.select(myCollectionB, new IPredicate<MyTypeB>() {
public boolean apply(MyTypeB objectOfB) {
return objectOfB.getName().equals(Predicate.predicateParams.toString());
}
}) == null;
}
});
L'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<MyType>() {
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 pubblicato questa risposta per la prima volta, e non riesco ancora a credere di raccogliere punti SO per questa risposta. Ad ogni modo, ora che Java 8 ha introdotto delle chiusure nella lingua, la mia risposta ora sarebbe considerevolmente diversa e più semplice. Con Java 8, non è necessaria una distinta classe di utilità statica. Quindi, se vuoi trovare il primo elemento che corrisponde al tuo predicato.
final UserService userService = ... // perhaps injected IoC
final Optional<UserModel> userOption = userCollection.stream().filter(u -> {
boolean isAuthorized = userService.isAuthorized(u);
return isAuthorized;
}).findFirst();
L'API JDK 8 per optional ha la capacità di get()
, isPresent()
, orElse(defaultUser)
, orElseGet(userSupplier)
e orElseThrow(exceptionSupplier)
, così come altre funzioni 'monadici' come map
, flatMap
efilter
.
Se si desidera semplicemente raccogliere tutti gli utenti che corrispondono al predicato, utilizzare il Collectors
per terminare il flusso nella raccolta desiderata.
final UserService userService = ... // perhaps injected IoC
final List<UserModel> userOption = userCollection.stream().filter(u -> {
boolean isAuthorized = userService.isAuthorized(u);
return isAuthorized;
}).collect(Collectors.toList());
Vedi qui per ulteriori esempi su come funzionano i flussi Java 8.
val authorized = for (user <- users if user.isAuthorized) yield user
Usa CollectionUtils.filter (Collection, Predicate) , di Apache Commons.
Il modo "migliore" è una richiesta troppo ampia. È "il più corto"? "Più veloce"? "Leggibile"? Filtro in atto o in un'altra raccolta?
Il modo più semplice (ma non più leggibile) è di iterarlo e utilizzare il metodo Iterator.remove ():
Iterator<Foo> 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 inventare un'interfaccia IPredicate, creare un'implementazione anonima di tale interfaccia e fare qualcosa del tipo:
CollectionUtils.filterInPlace(col,
new IPredicate<Foo>(){
public boolean keepIt(Foo foo) {
return foo.isBar();
}
});
dove filterInPlace () esegue l'iterazione della raccolta e chiama Predicate.keepIt () per sapere se l'istanza deve essere conservata nella raccolta.
Non vedo davvero una giustificazione per portare in una libreria di terze parti solo per questo compito.
stream()
funzionalità, ma non tutti possono giocare con i giocattoli più recenti: P
Prendi in considerazione le raccolte di Google per un framework di raccolte aggiornato che supporti i generici.
AGGIORNAMENTO : la libreria delle raccolte di Google è ora obsoleta. Dovresti usare invece l'ultima versione di Guava . Ha ancora le stesse estensioni al framework delle raccolte, incluso un meccanismo di filtraggio basato su un predicato.
Attendi Java 8:
List<Person> 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());
personList.removeIf(p -> p.age < 30);
Meno dettagliato. Inoltre, ho sentito parlare di iniziare a implementare API che accettano e restituiscono Stream
s piuttosto che Collection
s perché Stream
s sono molto utili e veloci ma andare da / verso di loro è lento.
Dalla prima versione di Java 8, potresti provare qualcosa del tipo:
Collection<T> collection = ...;
Stream<T> stream = collection.stream().filter(...);
Ad esempio, se avevi un elenco di numeri interi e desideri filtrare i numeri che sono> 10 e quindi stampare quei numeri sulla console, potresti fare qualcosa del tipo:
List<Integer> numbers = Arrays.asList(12, 74, 5, 8, 16);
numbers.stream().filter(n -> n > 10).forEach(System.out::println);
Mi butto RxJava sul ring, che è disponibile anche su Android . RxJava potrebbe non essere sempre l'opzione migliore, ma ti darà maggiore flessibilità se desideri aggiungere più trasformazioni alla tua raccolta o gestire errori durante il filtraggio.
Observable.from(Arrays.asList(1, 2, 3, 4, 5))
.filter(new Func1<Integer, Boolean>() {
public Boolean call(Integer i) {
return i % 2 != 0;
}
})
.subscribe(new Action1<Integer>() {
public void call(Integer i) {
System.out.println(i);
}
});
Produzione:
1
3
5
Maggiori dettagli su RxJava filter
sono disponibili qui .
Il set up:
public interface Predicate<T> {
public boolean filter(T t);
}
void filterCollection(Collection<T> col, Predicate<T> predicate) {
for (Iterator i = col.iterator(); i.hasNext();) {
T obj = i.next();
if (predicate.filter(obj)) {
i.remove();
}
}
}
L'utilizzo:
List<MyObject> myList = ...;
filterCollection(myList, new Predicate<MyObject>() {
public boolean filter(MyObject obj) {
return obj.shouldFilter();
}
});
Che ne dici di Java semplice e diretto
List<Customer> list ...;
List<Customer> 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 sola riga:
List<Customer> newList = list.stream().filter(c -> c.getName().equals("dd")).collect(toList());
Si noti che toList () viene importato staticamente
Sei sicuro di voler filtrare la raccolta stessa, anziché 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 incorporato e un Elenco modifiche usando le raccolte Eclipse .
List<Integer> jdkList = Arrays.asList(1, 2, 3, 4, 5);
MutableList<Integer> ecList = Lists.mutable.with(1, 2, 3, 4, 5);
Se si desidera filtrare i numeri inferiori a 3, ci si aspetterebbe i seguenti risultati.
List<Integer> selected = Lists.mutable.with(1, 2);
List<Integer> rejected = Lists.mutable.with(3, 4, 5);
Ecco come è possibile filtrare utilizzando 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, ecList.select(each -> each < 3));
Assert.assertEquals(rejected, ecList.reject(each -> each < 3));
Ecco come puoi filtrare usando una classe interna anonima come Predicate
.
Predicate<Integer> lessThan3 = new Predicate<Integer>()
{
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 mutableLists delle raccolte Eclipse utilizzando 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 alloca un oggetto per il predicato, usando la factory Predicates2 invece con il selectWith
metodo che accetta a Predicate2
.
Assert.assertEquals(
selected, ecList.selectWith(Predicates2.<Integer>lessThan(), 3));
A volte vuoi filtrare su una condizione negativa. Esiste un metodo speciale nelle raccolte Eclipse per quello chiamato reject
.
Assert.assertEquals(rejected, Iterate.reject(jdkList, lessThan3));
Assert.assertEquals(rejected, ecList.reject(lessThan3));
Il metodo partition
restituirà due raccolte, contenenti gli elementi selezionati da e rifiutati da Predicate
.
PartitionIterable<Integer> jdkPartitioned = Iterate.partition(jdkList, lessThan3);
Assert.assertEquals(selected, jdkPartitioned.getSelected());
Assert.assertEquals(rejected, jdkPartitioned.getRejected());
PartitionList<Integer> ecPartitioned = gscList.partition(lessThan3);
Assert.assertEquals(selected, ecPartitioned.getSelected());
Assert.assertEquals(rejected, ecPartitioned.getRejected());
Nota: sono un committer per le raccolte Eclipse.
removeIf
in un elenco o impostare per le primitive?
Con il DSL 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<String> collection = ...
for (Select<String> each : select(collection)) {
each.yield = each.value.length() > 3;
}
Collection<String> result = $result();
Data una raccolta di [The, quick, brown, fox, jumps, over, the, lazy, dog] questo si traduce in [quick, brown, jumps, over, lazy], ovvero tutte le stringhe più lunghe di tre caratteri.
Tutti gli stili di iterazione supportati da ForEach DSL sono
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 proprio quello che stai cercando.
Poiché java 9 Collectors.filtering
è abilitato:
public static <T, A, R>
Collector<T, ?, R> filtering(Predicate<? super T> predicate,
Collector<? super T, A, R> downstream)
Quindi il filtro dovrebbe essere:
collection.stream().collect(Collectors.filtering(predicate, collector))
Esempio:
List<Integer> oddNumbers = List.of(1, 19, 15, 10, -10).stream()
.collect(Collectors.filtering(i -> i % 2 == 1, Collectors.toList()));
Questo, combinato con la mancanza di chiusure reali, è la mia più grande lamentela per Java. Onestamente, la maggior parte dei metodi sopra menzionati sono piuttosto facili da leggere e DAVVERO efficienti; tuttavia, dopo aver trascorso del tempo con .Net, Erlang, ecc ... la comprensione dell'elenco integrata a livello linguistico rende tutto molto più pulito. Senza aggiunte a livello linguistico, Java non può essere pulito come molte altre lingue in quest'area.
Se le prestazioni sono un grosso problema, le raccolte di Google sono la strada da percorrere (o scrivi la tua semplice utility predicato). La sintassi Lambdaj è più leggibile per alcune persone, ma non è altrettanto efficiente.
E poi c'è una biblioteca che ho scritto. Ignorerò qualsiasi domanda relativa alla sua efficienza (sì, è così male) ...... Sì, conosco la sua chiara riflessione, e no non la uso effettivamente, ma funziona:
LinkedList<Person> list = ......
LinkedList<Person> filtered =
Query.from(list).where(Condition.ensure("age", Op.GTE, 21));
O
LinkedList<Person> list = ....
LinkedList<Person> 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 eseguire query sulla raccolta di bean Java.
Funzionalità chiave
Ho scritto una classe Iterable estesa che supporta l'applicazione di algoritmi funzionali senza copiare il contenuto della raccolta.
Uso:
List<Integer> myList = new ArrayList<Integer>(){ 1, 2, 3, 4, 5 }
Iterable<Integer> filtered = Iterable.wrap(myList).select(new Predicate1<Integer>()
{
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);
}
}
Usa Collection Query Engine (CQEngine) . È di gran lunga il modo più veloce per farlo.
Vedi anche: Come si interrogano le raccolte di oggetti in Java (criteri / simili a SQL)?
Alcune risposte davvero fantastiche qui. Io, vorrei mantenerlo il più semplice e leggibile possibile:
public abstract class AbstractFilter<T> {
/**
* 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<T> collection) {
if (CollectionUtils.isNotEmpty(collection)) {
Iterator<T> iterator = collection.iterator();
while (iterator.hasNext()) {
if (excludeItem(iterator.next())) {
iterator.remove();
}
}
}
}
}
La semplice soluzione pre-Java8:
ArrayList<Item> filtered = new ArrayList<Item>();
for (Item item : items) if (condition(item)) filtered.add(item);
Sfortunatamente questa soluzione non è completamente generica, producendo un elenco piuttosto che il tipo di raccolta data. Inoltre, portare librerie o scrivere funzioni che racchiudono questo codice mi sembra eccessivo a meno che la condizione non sia complessa, ma puoi scrivere una funzione per la condizione.
https://code.google.com/p/joquery/
Supporta diverse possibilità,
Raccolta data,
Collection<Dto> 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<Dto> query = CQ.<Dto>filter(testList)
.where()
.property("id").eq().value(1);
Collection<Dto> filtered = query.list();
Java 8
Filter<Dto> query = CQ.<Dto>filter(testList)
.where()
.property(Dto::getId)
.eq().value(1);
Collection<Dto> filtered = query.list();
Anche,
Filter<Dto> query = CQ.<Dto>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<Dto> query = CQ.<Dto>filter(testList)
.orderBy()
.property(Dto::getId)
.property(Dto::getName)
Collection<Dto> sorted = query.list();
Raggruppamento (disponibile anche per Java 7)
GroupQuery<Integer,Dto> query = CQ.<Dto,Dto>query(testList)
.group()
.groupBy(Dto::getId)
Collection<Grouping<Integer,Dto>> grouped = query.list();
Joins (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<LeftDto> leftList = new ArrayList<>();
Collection<RightDto> rightList = new ArrayList<>();
Può essere unito come,
Collection<JoinedDto> results = CQ.<LeftDto, LeftDto>query().from(leftList)
.<RightDto, JoinedDto>innerJoin(CQ.<RightDto, RightDto>query().from(rightList))
.on(LeftFyo::getId, RightDto::getLeftId)
.transformDirect(selection -> new JoinedDto(selection.getLeft().getText()
, selection.getLeft().getId()
, selection.getRight().getId())
)
.list();
espressioni
Filter<Dto> query = CQ.<Dto>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 che usa CollectionUtils
from spring e un'espressione lambda di Java 8 .
CollectionUtils.filter(list, p -> ((Person) p).getAge() > 16);
Questo è conciso e leggibile come qualsiasi altra alternativa che abbia mai visto (senza usare librerie basate sull'aspetto)
Primavera CollectionUtils è disponibile dalla versione primavera 4.0.2.RELEASE, e ricordare è necessario JDK 1.8 e livello linguistico 8+.
Utilizzando 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 raccolta product
interna myProducts
, se prod.price>10
, quindi aggiungere questo prodotto al nuovo elenco filtrato.
Avevo bisogno di filtrare un elenco in base ai valori già presenti nell'elenco. Ad esempio, rimuovere tutti i valori seguenti che è inferiore al valore corrente. {2 5 3 4 7 5} -> {2 5 7}. O ad esempio per rimuovere tutti i duplicati {3 5 4 2 3 5 6} -> {3 5 4 2 6}.
public class Filter {
public static <T> void List(List<T> list, Chooser<T> chooser) {
List<Integer> 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<Integer>() {
@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 <T> void List(List<T> list, Keeper<T> keeper) {
Iterator<T> iterator = list.iterator();
while (iterator.hasNext()) {
if (!keeper.keep(iterator.next())) {
iterator.remove();
}
}
}
public interface Keeper<E> {
boolean keep(E obj);
}
public interface Chooser<E> {
Keep choose(E left, E right);
}
public enum Keep {
LEFT, RIGHT, BOTH, NONE;
}
}
Questa ape verrà utilizzata in questo modo.
List<String> names = new ArrayList<>();
names.add("Anders");
names.add("Stefan");
names.add("Anders");
Filter.List(names, new Filter.Chooser<String>() {
@Override
public Filter.Keep choose(String left, String right) {
return left.equals(right) ? Filter.Keep.LEFT : Filter.Keep.BOTH;
}
});
Con Guava:
Collection<Integer> collection = Lists.newArrayList(1, 2, 3, 4, 5);
Iterators.removeIf(collection.iterator(), new Predicate<Integer>() {
@Override
public boolean apply(Integer i) {
return i % 2 == 0;
}
});
System.out.println(collection); // Prints 1, 3, 5
In Java 8, è possibile utilizzare direttamente questo metodo di filtro e quindi farlo.
List<String> lines = Arrays.asList("java", "pramod", "example");
List<String> result = lines.stream()
.filter(line -> !"pramod".equals(line))
.collect(Collectors.toList());
result.forEach(System.out::println);