Filtern einer Java-Sammlung nach einer Liste

1. Übersicht

Das Filtern einer Sammlung nach einer Liste ist ein gängiges Geschäftslogikszenario. Es gibt viele Möglichkeiten, dies zu erreichen. Einige können jedoch zu unzureichenden Lösungen führen, wenn sie nicht ordnungsgemäß ausgeführt werden.

In diesem Tutorial werden einige Filterimplementierungen verglichen und ihre Vor- und Nachteile erläutert .

2. Verwenden einer For-Each- Schleife

Wir beginnen mit der klassischsten Syntax, einer for-each-Schleife.

Für dieses und alle anderen Beispiele in diesem Artikel verwenden wir die folgende Klasse:

public class Employee { private Integer employeeNumber; private String name; private Integer departmentId; //Standard constructor, getters and setters. }

Der Einfachheit halber werden wir für alle Beispiele auch die folgenden Methoden verwenden:

private List buildEmployeeList() { return Arrays.asList( new Employee(1, "Mike", 1), new Employee(2, "John", 1), new Employee(3, "Mary", 1), new Employee(4, "Joe", 2), new Employee(5, "Nicole", 2), new Employee(6, "Alice", 2), new Employee(7, "Bob", 3), new Employee(8, "Scarlett", 3)); } private List employeeNameFilter() { return Arrays.asList("Alice", "Mike", "Bob"); }

In unserem Beispiel filtern wir die erste Liste der Mitarbeiter basierend auf der zweiten Liste mit Mitarbeiternamen , um nur die Mitarbeiter mit diesen spezifischen Namen zu finden.

Lassen Sie uns nun den traditionellen Ansatz betrachten - beide Listen nach Übereinstimmungen durchsuchen:

@Test public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingForEachLoop() { List filteredList = new ArrayList(); List originalList = buildEmployeeList(); List nameFilter = employeeNameFilter(); for (Employee employee : originalList) { for (String name : nameFilter) { if (employee.getName().equals(name)) { filteredList.add(employee); // break; } } } assertThat(filteredList.size(), is(nameFilter.size())); }

Dies ist eine einfache Syntax, aber sie ist ziemlich ausführlich und tatsächlich ziemlich ineffizient. Einfach ausgedrückt, durchläuft es das kartesische Produkt der beiden Sätze , um unsere Antwort zu erhalten.

Selbst das Hinzufügen einer Pause zum vorzeitigen Beenden wird im Durchschnitt immer noch in derselben Reihenfolge wie ein kartesisches Produkt wiederholt.

Wenn wir die Größe der Mitarbeiterliste n nennen, wird nameFilter in der Bestellung genauso groß sein, was uns eine O (n2) -Klassifizierung gibt.

3. Verwenden von Streams und List # enthält

Wir werden jetzt die vorherige Methode umgestalten, indem wir Lambdas verwenden, um die Syntax zu vereinfachen und die Lesbarkeit zu verbessern . Verwenden wir auch die Methode List # enthält als Lambda-Filter :

@Test public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambda() { List filteredList; List originalList = buildEmployeeList(); List nameFilter = employeeNameFilter(); filteredList = originalList.stream() .filter(employee -> nameFilter.contains(employee.getName())) .collect(Collectors.toList()); assertThat(filteredList.size(), is(nameFilter.size())); }

Durch die Verwendung der Stream-API wurde die Lesbarkeit erheblich verbessert, aber unser Code bleibt genauso ineffizient wie unsere vorherige Methode, da er das kartesische Produkt immer noch intern durchläuft . Wir haben also die gleiche O (n2) -Klassifikation.

4. Verwenden von Streams mit HashSet

Um die Leistung zu verbessern, müssen wir die HashSet # enthält- Methode verwenden. Diese Methode unterscheidet sich von List # enthält, weil sie eine Hash-Code- Suche durchführt und uns eine zeitlich konstante Anzahl von Operationen gibt:

@Test public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambdaAndHashSet() { List filteredList; List originalList = buildEmployeeList(); Set nameFilterSet = employeeNameFilter().stream().collect(Collectors.toSet()); filteredList = originalList.stream() .filter(employee -> nameFilterSet.contains(employee.getName())) .collect(Collectors.toList()); assertThat(filteredList.size(), is(nameFilterSet.size())); }

Durch die Verwendung von HashSet hat sich unsere Codeeffizienz erheblich verbessert, ohne die Lesbarkeit zu beeinträchtigen. Da HashSet # Läufe in konstanter Zeit enthält, haben wir unsere Klassifizierung auf O (n) verbessert .

5. Schlussfolgerung

In diesem kurzen Tutorial haben wir gelernt, wie eine Sammlung nach einer Werteliste gefiltert wird und welche Nachteile die Verwendung der möglicherweise einfachsten Methode hat.

Wir müssen immer die Effizienz berücksichtigen, da unser Code möglicherweise in großen Datenmengen ausgeführt wird und Leistungsprobleme in solchen Umgebungen katastrophale Folgen haben können.

Der gesamte in diesem Artikel vorgestellte Code ist auf GitHub verfügbar.