771 Stimmen

Wie filtert man eine Java-Sammlung (basierend auf einem Prädikat)?

Ich möchte einen Filter java.util.Collection auf der Grundlage eines Prädikats.

852voto

Mario Fusco Punkte 12920

Java 8 ( 2014 ) löst dieses Problem mit Streams und Lambdas in einer einzigen Codezeile:

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

Hier ist ein Lehrgang .

使用方法 Collection#removeIf um die Sammlung an Ort und Stelle zu ändern. (Beachte: In diesem Fall entfernt das Prädikat Objekte, die das Prädikat erfüllen):

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

lambdaj ermöglicht das Filtern von Sammlungen ohne das Schreiben von Schleifen oder inneren Klassen:

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

Können Sie sich etwas Lesbareres vorstellen?

Haftungsausschluss: Ich bin ein Mitarbeiter von lambdaj

229voto

Alan Punkte 7006

Angenommen, Sie verwenden Java 1.5 und dass Sie nicht hinzufügen können Google-Sammlungen Ich würde etwas ganz Ähnliches tun, wie die Google-Leute es getan haben. Dies ist eine leichte Abwandlung von Jons Kommentaren.

Fügen Sie zunächst diese Schnittstelle zu Ihrer Codebasis hinzu.

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

Seine Implementierer können antworten, wenn ein bestimmtes Prädikat auf einen bestimmten Typ zutrifft. Zum Beispiel: Wenn T waren User y AuthorizedUserPredicate<User> implementiert IPredicate<T> entonces AuthorizedUserPredicate#apply gibt zurück, ob der übergebene User autorisiert ist.

In einer Utility-Klasse könnte man dann sagen

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

Unter der Annahme, dass Sie die oben genannten Möglichkeiten nutzen, könnten Sie also

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

Wenn die Leistung der linearen Prüfung von Belang ist, dann könnte ich ein Domänenobjekt haben wollen, das die Zielsammlung hat. Das Domänenobjekt, das die Zielsammlung hat, würde Filterlogik für die Methoden haben, die die Zielsammlung initialisieren, hinzufügen und einstellen.

UPDATE:

In der Dienstprogrammklasse (sagen wir Predicate) habe ich eine Select-Methode mit einer Option für den Standardwert hinzugefügt, wenn das Predicate nicht den erwarteten Wert zurückgibt, und auch eine statische Eigenschaft für Parameter, die innerhalb des neuen IPredicate verwendet werden soll.

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

Das folgende Beispiel sucht nach fehlenden Objekten zwischen Sammlungen:

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

Das folgende Beispiel sucht nach einer Instanz in einer Sammlung und gibt das erste Element der Sammlung als Standardwert zurück, wenn die Instanz nicht gefunden wird:

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

UPDATE (nach der Veröffentlichung von Java 8):

Es ist schon einige Jahre her, dass ich (Alan) diese Antwort zum ersten Mal gepostet habe, und ich kann immer noch nicht glauben, dass ich SO viele Punkte für diese Antwort sammle. Jedenfalls würde meine Antwort jetzt, da Java 8 die Closures in die Sprache eingeführt hat, wesentlich anders und einfacher ausfallen. Mit Java 8 gibt es keine Notwendigkeit mehr für eine eigene statische Utility-Klasse. Wenn Sie also das 1. Element finden wollen, das Ihrem Prädikat entspricht.

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

Die JDK 8-API für Optionals bietet die Möglichkeit get() , isPresent() , orElse(defaultUser) , orElseGet(userSupplier) y orElseThrow(exceptionSupplier) sowie andere "monadische" Funktionen wie map , flatMap y filter .

Wenn Sie einfach alle Benutzer sammeln wollen, die dem Prädikat entsprechen, dann verwenden Sie die Collectors um den Stream in der gewünschten Sammlung zu beenden.

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

Siehe aquí für weitere Beispiele, wie Java 8 Streams funktionieren.

96voto

Kevin Wong Punkte 14146

使用方法 CollectionUtils.filter(Sammlung,Prädikat) , von Apache Commons.

68voto

Vladimir Dyuzhev Punkte 17849

Der "beste" Weg ist eine zu weit gefasste Forderung. Ist es "der kürzeste"? "Schnellste"? "Lesbar"? An Ort und Stelle oder in eine andere Sammlung filtern?

Die einfachste (aber nicht sehr lesbare) Methode ist die Iteration und die Verwendung der Methode Iterator.remove():

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

Um es lesbarer zu machen, können Sie es in eine Utility-Methode verpacken. Dann erfinden Sie eine IPredicate-Schnittstelle, erstellen eine anonyme Implementierung dieser Schnittstelle und tun etwas wie:

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

wobei filterInPlace() die Sammlung durchläuft und Predicate.keepIt() aufruft, um zu erfahren, ob die Instanz in der Sammlung zu behalten ist.

Ich sehe nicht wirklich eine Rechtfertigung dafür, eine Bibliothek eines Drittanbieters nur für diese Aufgabe heranzuziehen.

62voto

Heath Borders Punkte 29263

Erwägen Sie Google-Sammlungen für ein aktualisiertes Collections-Framework, das Generika unterstützt.

UPDATE : Die Google Collections Bibliothek ist jetzt veraltet. Sie sollten die neueste Version von Guave stattdessen. Es verfügt über dieselben Erweiterungen des Collections-Frameworks, einschließlich eines Mechanismus zur Filterung auf der Grundlage eines Prädikats.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X