objetos - ¿Cuál es la mejor manera de filtrar una colección Java?
listas en java (27)
Quiero filtrar un java.util.Collection
basado en un predicado.
"Mejor" es una solicitud demasiado amplia. ¿Es "el más corto"? "Lo más rápido"? "Legible"? ¿Filtrar en su lugar o en otra colección?
La forma más simple (pero no más legible) es iterarlo y usar el método Iterator.remove ():
Iterator<Foo> it = col.iterator();
while( it.hasNext() ) {
Foo foo = it.next();
if( !condition(foo) ) it.remove();
}
Ahora, para hacerlo más legible, puede envolverlo en un método de utilidad. Luego invente una interfaz IPredicate, cree una implementación anónima de esa interfaz y haga algo como:
CollectionUtils.filterInPlace(col,
new IPredicate<Foo>(){
public boolean keepIt(Foo foo) {
return foo.isBar();
}
});
donde filterInPlace () itera la colección y llama a Predicate.keepIt () para saber si la instancia se mantendrá en la colección.
Realmente no veo una justificación para traer una biblioteca de terceros solo para esta tarea.
¿Está seguro de que desea filtrar la Colección en sí, en lugar de un iterador?
ver org.apache.commons.collections.iterators.FilterIterator
o usando la versión 4 de apache commons org.apache.commons.collections4.iterators.FilterIterator
¿Qué tal un Java sencillo y sencillo?
List<Customer> list ...;
List<Customer> newList = new ArrayList<>();
for (Customer c : list){
if (c.getName().equals("dd")) newList.add(c);
}
Simple, legible y fácil (¡y funciona en Android!) Pero si está utilizando Java 8, puede hacerlo en una línea dulce:
List<Customer> newList = list.stream().filter(c -> c.getName().equals("dd")).collect(toList());
Tenga en cuenta que toList () se importa estáticamente
Algunas respuestas realmente geniales aquí. Yo, me gustaría mantener las cosas lo más simples y legibles posible:
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();
}
}
}
}
}
Con el DSE ForEach puedes escribir
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();
Dada una colección de [The, quick, brown, fox, saltos, over, the, perezoso, dog], esto resulta en [quick, brown, saltos, over, flojo], es decir, todas las cadenas de más de tres caracteres.
Todos los estilos de iteración soportados por ForEach DSL son
-
AllSatisfy
-
AnySatisfy
-
Collect
-
Counnt
-
CutPieces
-
Detect
-
GroupedBy
-
IndexOf
-
InjectInto
-
Reject
-
Select
Para obtener más detalles, consulte https://www.iam.unibe.ch/scg/svn_repos/Sources/ForEach
Con guayaba:
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
Considere Google Collections para un marco actualizado de Collections que admita genéricos.
ACTUALIZACIÓN : La biblioteca de colecciones de Google ahora está en desuso. Deberías usar la última versión de Guava lugar. Todavía tiene todas las mismas extensiones para el marco de colecciones, incluido un mecanismo para filtrar según un predicado.
Desde java 9 Collectors.filtering
está habilitado:
public static <T, A, R>
Collector<T, ?, R> filtering(Predicate<? super T> predicate,
Collector<? super T, A, R> downstream)
Por lo tanto, el filtrado debe ser:
collection.stream().collect(Collectors.filtering(predicate, collector))
Ejemplo:
List<Integer> oddNumbers = List.of(1, 19, 15, 10, -10).stream()
.collect(Collectors.filtering(i -> i % 2 == 1, Collectors.toList()));
Desde el lanzamiento temprano de Java 8, puedes probar algo como:
Collection<T> collection = ...;
Stream<T> stream = collection.stream().filter(...);
Por ejemplo, si tuviera una lista de enteros y quisiera filtrar los números que son> 10 y luego imprimir esos números en la consola, podría hacer algo como:
List<Integer> numbers = Arrays.asList(12, 74, 5, 8, 16);
numbers.stream().filter(n -> n > 10).forEach(System.out::println);
El método Collections2.filter(Collection,Predicate) en la biblioteca de guayabas de Google hace exactamente lo que está buscando.
Escribí una clase Iterable extendida que admite la aplicación de algoritmos funcionales sin copiar el contenido de la colección.
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);
}
El código de arriba realmente se ejecutará
for( int n : myList )
{
if( n % 2 == 0 )
{
System.out.println(n);
}
}
Espera a 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());
Esto, combinado con la falta de cierres reales, es mi mayor queja para Java. Honestamente, la mayoría de los métodos mencionados anteriormente son bastante fáciles de leer y REALMENTE eficientes; sin embargo, después de pasar tiempo con .Net, Erlang, etc ... la comprensión de la lista integrada en el nivel del idioma hace que todo sea mucho más limpio. Sin adiciones en el nivel de idioma, Java no puede ser tan limpio como muchos otros idiomas en esta área.
Si el rendimiento es una gran preocupación, las colecciones de Google son el camino a seguir (o escriba su propia utilidad de predicado simple). La sintaxis de Lambdaj es más legible para algunas personas, pero no es tan eficiente.
Y luego hay una biblioteca que escribí. Ignoraré cualquier pregunta con respecto a su eficiencia (sí, es tan malo) ... Sí, sé que está claramente basado en la reflexión y no, en realidad no lo uso, pero funciona:
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/ se adapta mejor a sus necesidades.
JFilter es una biblioteca de código abierto simple y de alto rendimiento para consultar la colección de beans Java.
Características clave
- Soporte de las propiedades de colección (java.util.Collection, java.util.Map y Array).
- Soporte de colección dentro de colección de cualquier profundidad.
- Soporte de consultas internas.
- Soporte de consultas parametrizadas.
- Puede filtrar 1 millón de registros en pocos 100 ms.
- El filtro (consulta) se da en formato json simple, es como las consultas Mangodb. Los siguientes son algunos ejemplos.
- {"id": {"$ le": "10"}
- donde la propiedad de id de objeto es menor que 10.
- {"id": {"$ in": ["0", "100"]}}
- donde la propiedad de id de objeto es 0 o 100.
- {"lineItems": {"lineAmount": "1"}}
- donde la propiedad de colección lineItems de tipo parametrizado tiene lineAmount igual a 1.
- {"$ and": [{"id": "0"}, {"billingAddress": {"city": "DEL"}}]}
- donde id propiedad es 0 y billingAddress.city es DEL.
- {"lineItems": {"tax": {"key": {"code": "GST"}, "value": {"$ gt": "1.01"}}}}
- donde la propiedad de colección lineItems de tipo parametrizado que tiene impuestos propiedad de tipo de mapa de tipo parametrizado tiene un código igual a GST mayor que 1.01
- {''$ or'': [{''code'': ''10''}, {''skus'': {''$ and'': [{''price'': {''$ in'': [''20'', ''40'']} }, {''código'': ''RedApple''}]}}]}
- Seleccione todos los productos en los que el código del producto sea 10 o el precio sku en 20 y 40 y el código sku sea "RedApple".
Java 8 ( 2014 ) resuelve este problema utilizando flujos y lambdas en una línea de código:
List<Person> beerDrinkers = persons.stream()
.filter(p -> p.getAge() > 16).collect(Collectors.toList());
Aquí hay un tutorial .
Use Collection#removeIf
para modificar la colección en su lugar. (Aviso: en este caso, el predicado eliminará los objetos que satisfagan el predicado):
persons.removeIf(p -> p.getAge() <= 16);
lambdaj permite filtrar colecciones sin escribir bucles o clases internas:
List<Person> beerDrinkers = select(persons, having(on(Person.class).getAge(),
greaterThan(16)));
¿Te imaginas algo más legible?
Descargo de responsabilidad: Soy un colaborador en lambdaj
La puesta en marcha:
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();
}
}
}
El uso:
List<MyObject> myList = ...;
filterCollection(myList, new Predicate<MyObject>() {
public boolean filter(MyObject obj) {
return obj.shouldFilter();
}
});
La solución simple pre-Java8:
ArrayList<Item> filtered = new ArrayList<Item>();
for (Item item : items) if (condition(item)) filtered.add(item);
Desafortunadamente, esta solución no es totalmente genérica, generando una lista en lugar del tipo de la colección dada. Además, traer bibliotecas o escribir funciones que envuelven este código me parece excesivo a menos que la condición sea compleja, pero luego puede escribir una función para la condición.
Mi respuesta se basa en eso de Kevin Wong, aquí como una sola línea usando CollectionUtils
from spring y una expresión lambda de Java 8.
CollectionUtils.filter(list, p -> ((Person) p).getAge() > 16);
Esto es tan conciso y legible como cualquier alternativa que haya visto (sin usar bibliotecas basadas en aspectos)
Spring CollectionUtils está disponible a partir de la versión 4.0.2.RELEASE de Spring, y recuerda que necesitas JDK 1.8 y nivel de idioma 8+.
Necesitaba filtrar una lista según los valores ya presentes en la lista. Por ejemplo, elimine todos los valores siguientes que sean menores que el valor actual. {2 5 3 4 7 5} -> {2 5 7}. O por ejemplo, para eliminar todos los duplicados {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;
}
}
Esto se utilizará de esta manera.
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;
}
});
Suponiendo que está utilizando Java 1.5 , y que no puede agregar Google Collections , haría algo muy similar a lo que hicieron los chicos de Google. Esta es una ligera variación en los comentarios de Jon.
Primero agrega esta interfaz a tu base de código.
public interface IPredicate<T> { boolean apply(T type); }
Sus implementadores pueden responder cuando cierto predicado es cierto de cierto tipo. Por ejemplo, si T
fuera User
y AuthorizedUserPredicate<User>
implementa IPredicate<T>
, AuthorizedUserPredicate#apply
devuelve si el User
aprobado está autorizado.
Luego, en alguna clase de utilidad, se podría decir
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;
}
Entonces, asumiendo que tiene el uso de lo anterior podría ser
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);
Si el rendimiento en la verificación lineal es preocupante, es posible que desee tener un objeto de dominio que tenga la colección objetivo. El objeto de dominio que tiene la colección objetivo tendría lógica de filtrado para los métodos que inicializan, agregan y configuran la colección objetivo.
ACTUALIZAR:
En la clase de utilidad (digamos Predicado), agregué un método de selección con una opción para el valor predeterminado cuando el predicado no devuelve el valor esperado, y también una propiedad estática para los parámetros que se usarán dentro de la nueva 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;
}
}
El siguiente ejemplo busca objetos faltantes entre colecciones:
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;
}
});
El siguiente ejemplo busca una instancia en una colección y devuelve el primer elemento de la colección como valor predeterminado cuando no se encuentra la instancia:
MyType myObject = Predicate.select(collectionOfMyType, new IPredicate<MyType>() {
public boolean apply(MyType objectOfMyType) {
return objectOfMyType.isDefault();
}}, collectionOfMyType.get(0));
ACTUALIZACIÓN (después del lanzamiento de Java 8):
Han pasado varios años desde que (Alan) publiqué esta respuesta por primera vez, y aún no puedo creer que esté acumulando SO puntos para esta respuesta. En cualquier caso, ahora que Java 8 ha introducido cierres al lenguaje, mi respuesta ahora sería considerablemente diferente y más simple. Con Java 8, no hay necesidad de una clase de utilidad estática distinta. Así que si quieres encontrar el primer elemento que coincida con tu predicado.
final UserService userService = ... // perhaps injected IoC
final Optional<UserModel> userOption = userCollection.stream().filter(u -> {
boolean isAuthorized = userService.isAuthorized(u);
return isAuthorized;
}).findFirst();
La API JDK 8 para opcionales tiene la capacidad de get()
, isPresent()
, orElse(defaultUser)
, orElseGet(userSupplier)
y orElseThrow(exceptionSupplier)
, así como otras funciones ''monádicas'' como el map
, flatMap
y filter
.
Si desea simplemente recopilar todos los usuarios que coinciden con el predicado, utilice los Collectors
para terminar la secuencia en la recopilación deseada.
final UserService userService = ... // perhaps injected IoC
final List<UserModel> userOption = userCollection.stream().filter(u -> {
boolean isAuthorized = userService.isAuthorized(u);
return isAuthorized;
}).collect(Collectors.toList());
Consulte here para obtener más ejemplos sobre cómo funcionan las secuencias de Java 8.
Usando java 8
, específicamente la lambda expression
, puedes hacerlo simplemente como en el siguiente ejemplo:
myProducts.stream().filter(prod -> prod.price>10).collect(Collectors.toList())
donde para cada product
dentro de la colección myProducts
, si prod.price>10
, agregue este producto a la nueva lista filtrada.
Utilice CollectionUtils.filter(Collection,Predicate) , de Apache Commons.
Utilice el motor de consulta de colección (CQEngine) . Es, con mucho, la forma más rápida de hacer esto.
Vea también: ¿Cómo consultar las colecciones de objetos en Java (Criterios / como SQL)?
Veamos cómo filtrar una lista JDK incorporada y una lista MutableList usando Eclipse Collections (anteriormente GS Collections ).
List<Integer> jdkList = Arrays.asList(1, 2, 3, 4, 5);
MutableList<Integer> ecList = Lists.mutable.with(1, 2, 3, 4, 5);
Si quisiera filtrar los números menores de 3, esperaría las siguientes salidas.
List<Integer> selected = Lists.mutable.with(1, 2);
List<Integer> rejected = Lists.mutable.with(3, 4, 5);
Aquí se explica cómo puede filtrar utilizando una clase interna anónima como el 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));
Aquí hay algunas alternativas para filtrar listas JDK y colecciones mutativas de Eclipse usando la fábrica de Predicates .
Assert.assertEquals(selected, Iterate.select(jdkList, Predicates.lessThan(3)));
Assert.assertEquals(selected, ecList.select(Predicates.lessThan(3)));
Aquí hay una versión que no asigna un objeto para el predicado, utilizando la fábrica de Predicates2 lugar del método selectWith
que toma un Predicate2
.
Assert.assertEquals(
selected, ecList.selectWith(Predicates2.<Integer>lessThan(), 3));
A veces quieres filtrar en una condición negativa. Hay un método especial en Eclipse Collections para el llamado reject
.
Assert.assertEquals(rejected, Iterate.reject(jdkList, lessThan3));
Assert.assertEquals(rejected, ecList.reject(lessThan3));
Aquí le mostramos cómo puede filtrar utilizando una lambda de Java 8 como 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
método devolverá dos colecciones, que contienen los elementos seleccionados y rechazados por el 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: Soy un comendador de Eclipse Collections.
https://code.google.com/p/joquery/
Soporta diferentes posibilidades,
Colección dada,
Collection<Dto> testList = new ArrayList<>();
de tipo,
class Dto
{
private int id;
private String text;
public int getId()
{
return id;
}
public int getText()
{
return text;
}
}
Filtrar
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();
También,
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"});
Clasificación (también disponible para Java 7)
Filter<Dto> query = CQ.<Dto>filter(testList)
.orderBy()
.property(Dto::getId)
.property(Dto::getName)
Collection<Dto> sorted = query.list();
Agrupación (también disponible para Java 7)
GroupQuery<Integer,Dto> query = CQ.<Dto,Dto>query(testList)
.group()
.groupBy(Dto::getId)
Collection<Grouping<Integer,Dto>> grouped = query.list();
Uniones (también disponibles para Java 7)
Dado,
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<>();
Se puede unir como,
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();
Expresiones
Filter<Dto> query = CQ.<Dto>filter()
.from(testList)
.where()
.exec(s -> s.getId() + 1).eq().value(2);
RxJava en el ring, que también está disponible en Android . RxJava puede no ser siempre la mejor opción, pero le dará más flexibilidad si desea agregar más transformaciones en su colección o manejar los errores mientras filtra.
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);
}
});
Salida:
1
3
5
Más detalles sobre el filter
de RxJava se pueden encontrar here .
In Java 8, You can directly use this filter method and then do that.
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);