Pregunta ¿Cuál es la mejor manera de filtrar una Colección Java?


Quiero filtrar un java.util.Collection basado en un predicado.


559
2017-09-23 16:26


origen


Respuestas:


Java 8 (2014) resuelve este problema usando streams y lambdas en una línea de código:

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

Aquí está un tutorial.

Utilizar Collection#removeIf para modificar la colección en su lugar. (Aviso: en este caso, el predicado eliminará los objetos que satisfacen 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)));

¿Puedes imaginar algo más legible?

Renuncia: Soy colaborador en lambdaj


531
2017-09-06 13:37



Asumiendo que estás usando Java 1.5y que no puedes agregar Colecciones de GoogleHaría algo muy parecido a lo que hicieron los chicos de Google. Esta es una pequeña variación de los comentarios de Jon.

Primero agregue esta interfaz a su código base.

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

Sus implementadores pueden responder cuando cierto predicado es cierto de cierto tipo. P.ej. Si T fueron User y AuthorizedUserPredicate<User> implementos IPredicate<T>, entonces AuthorizedUserPredicate#apply devuelve si el aprobado User está autorizado.

Luego, en alguna clase de utilidad, podrías 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;
}

Por lo tanto, suponiendo 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 motivo de preocupación, entonces podría querer tener un objeto de dominio que tenga la colección de destino. El objeto de dominio que tiene la colección de destino tendrá una lógica de filtrado para los métodos que inicializan, agregan y configuran la colección de destino.

ACTUALIZAR:

En la clase de utilidad (digamos Predicado), he agregado 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 que params se use dentro del nuevo IPredicado.

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 de la versión de Java 8):

Han pasado varios años desde que (Alan) primero publique esta respuesta, y todavía no puedo creer que esté recogiendo SO puntos para esta respuesta. En cualquier caso, ahora que Java 8 ha introducido cierres en el 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. Entonces, 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 map, flatMap y filter.

Si simplemente desea recopilar todos los usuarios que coinciden con el predicado, utilice el Collectors para terminar la secuencia en la colecció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());

Ver aquí para obtener más ejemplos sobre cómo funcionan las secuencias de Java 8.


213
2017-09-23 16:41



Utilizar CollectionUtils.filter (Colección, Predicado), de Apache Commons.


87
2017-09-23 16:28



La "mejor" manera es una solicitud demasiado amplia. ¿Es "más corto"? "Lo más rápido"? "Legible"? ¿Filtrar en su lugar o en otra colección?

La forma más sencilla (pero no la más legible) es iterarla 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 que sea 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 guardará en la colección.

Realmente no veo una justificación para traer una biblioteca de terceros solo para esta tarea.


60
2017-09-23 16:41



Considerar Colecciones de Google para un marco de colecciones actualizado que sea compatible con los genéricos.

ACTUALIZAR: La biblioteca de colecciones de google ahora está en desuso. Deberías usar la última versión de Guayaba en lugar. Todavía tiene todas las mismas extensiones para el marco de colecciones, incluido un mecanismo para filtrar basado en un predicado.


60
2017-09-23 16:29



Espere 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());

23
2017-08-29 10:50



Desde el lanzamiento anticipado de Java 8, puedes probar algo como:

Collection<T> collection = ...;
Stream<T> stream = collection.stream().filter(...);

Por ejemplo, si tiene una lista de enteros y quiere 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);

10
2017-10-27 21:41



Yo lanzaré RxJava en el ring, que también está disponible en Androide. 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 errores durante el filtrado.

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 RxJava's filter puede ser encontrado aquí.


9
2017-07-24 02:13



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();
  }
});

6
2017-09-23 16:41



¿Qué tal un poco de Java simple y directo?

 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 sola 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


6
2018-05-12 05:03