Zwei Listen in Java vergleichen (gemeinsame, fehlende und gleiche Elemente)
Zwei Listen zu vergleichen kommt in Java ständig vor: Datenbankeinträge synchronisieren, API-Antworten in Tests prüfen oder herausfinden, welche IDs zwischen zwei Exporten hinzugekommen oder weggefallen sind. Je nach Frage brauchen Sie ein anderes Werkzeug:
- Sind die Listen gleich? – mit oder ohne Beachtung von Reihenfolge und Duplikaten
- Welche Elemente stehen in beiden Listen? – die Schnittmenge
- Welche Elemente stehen nur in einer Liste? – die Differenz
Diese Anleitung behandelt jeden Fall mit der Standardbibliothek (Java 16+ für Stream.toList() und Records) sowie mit Alternativen aus Apache Commons und Guava. Alle Codebeispiele verwenden diese Importe:
import java.util.*;
import java.util.stream.*;
Sie müssen zwei Listen nur einmal vergleichen? Fügen Sie sie in das kostenlose Tool zum Vergleichen zweier Listen ein und sehen Sie, was nur in A, nur in B und in beiden steht – ganz ohne Code.
1. Prüfen, ob zwei Listen gleich sind
List.equals() liefert nur dann true, wenn beide Listen dieselben Elemente in derselben Reihenfolge enthalten:
List<Integer> a = List.of(1, 2, 3);
List<Integer> b = List.of(1, 2, 3);
List<Integer> c = List.of(3, 2, 1);
System.out.println(a.equals(b)); // true
System.out.println(a.equals(c)); // false – same elements, different order
Die Elemente werden mit ihrer eigenen equals()-Methode verglichen. Das funktioniert also für Strings, Zahlen und jede Klasse, die equals() korrekt implementiert. Auch über Implementierungen hinweg: Eine ArrayList und eine LinkedList mit denselben Elementen sind gleich.
2. Listen ohne Beachtung der Reihenfolge vergleichen
Wie Sie die Reihenfolge ignorieren, hängt davon ab, ob Duplikate eine Rolle spielen.
Gleiche Elemente mit gleicher Häufigkeit (Duplikate zählen) – sortierte Kopien vergleichen:
List<Integer> x = Arrays.asList(3, 1, 2, 2);
List<Integer> y = Arrays.asList(2, 3, 2, 1);
List<Integer> sortedX = new ArrayList<>(x);
List<Integer> sortedY = new ArrayList<>(y);
Collections.sort(sortedX);
Collections.sort(sortedY);
System.out.println(sortedX.equals(sortedY)); // true
Lassen sich die Elemente nicht sortieren, vergleichen Sie stattdessen Häufigkeits-Maps:
Map<Integer, Long> countX = x.stream()
.collect(Collectors.groupingBy(i -> i, Collectors.counting()));
Map<Integer, Long> countY = y.stream()
.collect(Collectors.groupingBy(i -> i, Collectors.counting()));
System.out.println(countX.equals(countY)); // true
Gleiche unterschiedliche Elemente (Duplikate egal) – Sets vergleichen:
List<Integer> z = List.of(1, 2, 3);
System.out.println(new HashSet<>(x).equals(new HashSet<>(z))); // true
3. Gemeinsame Elemente (Schnittmenge) mit retainAll finden
retainAll() entfernt alle Elemente, die nicht in der anderen Collection stehen. Die Methode verändert die Liste, auf der sie aufgerufen wird – arbeiten Sie also mit einer Kopie:
List<String> listA = List.of("apple", "banana", "cherry", "date");
List<String> listB = List.of("banana", "date", "fig");
List<String> common = new ArrayList<>(listA);
common.retainAll(new HashSet<>(listB));
System.out.println(common); // [banana, date]
Die Kopie in eine neue ArrayList ist noch aus einem zweiten Grund wichtig: Mit List.of() oder Arrays.asList() erzeugte Listen unterstützen kein Entfernen, und retainAll() wirft dort eine UnsupportedOperationException.
4. Fehlende Elemente (Differenz) mit removeAll finden
removeAll() ist das Gegenteil: Die Methode entfernt alles, was in der anderen Collection steht, und lässt die Elemente übrig, die nur in der ersten Liste vorkommen:
List<String> onlyInA = new ArrayList<>(listA);
onlyInA.removeAll(new HashSet<>(listB));
System.out.println(onlyInA); // [apple, cherry]
List<String> onlyInB = new ArrayList<>(listB);
onlyInB.removeAll(new HashSet<>(listA));
System.out.println(onlyInB); // [fig]
Beide Methoden behalten die ursprüngliche Reihenfolge der Ausgangsliste bei, und übrig bleibende Elemente behalten ihre Duplikate.
5. Dasselbe mit Streams
Streams verändern die Quelllisten nicht und lesen sich gut, wenn Sie zusätzliche Filter brauchen:
Set<String> setB = new HashSet<>(listB);
List<String> onlyInA = listA.stream()
.filter(item -> !setB.contains(item))
.toList(); // Java 16+; use collect(Collectors.toList()) before that
List<String> inBoth = listA.stream()
.filter(setB::contains)
.toList();
System.out.println(onlyInA); // [apple, cherry]
System.out.println(inBoth); // [banana, date]
Um die Elemente zu erhalten, die in genau einer der Listen stehen (die symmetrische Differenz), kombinieren Sie beide Richtungen:
Set<String> setA = new HashSet<>(listA);
List<String> inOnlyOne = Stream.concat(
listA.stream().filter(item -> !setB.contains(item)),
listB.stream().filter(item -> !setA.contains(item)))
.toList();
System.out.println(inOnlyOne); // [apple, cherry, fig]
Für einen Vergleich ohne Beachtung der Groß-/Kleinschreibung normalisieren Sie vor dem Aufbau des Sets – zum Beispiel listB.stream().map(String::toLowerCase).collect(Collectors.toSet()) – und schreiben jedes geprüfte Element ebenfalls klein.
6. Listen von Objekten vergleichen
Collections vergleichen Objekte mit equals() und – bei Hash-basierten Collections – mit hashCode(). Java-Records erzeugen beides aus ihren Feldern und funktionieren daher sofort:
record Customer(int id, String name) {}
List<Customer> oldCustomers = List.of(new Customer(1, "Ann"), new Customer(2, "Bob"));
List<Customer> newCustomers = List.of(new Customer(2, "Bob"), new Customer(3, "Cy"));
Set<Customer> newSet = new HashSet<>(newCustomers);
List<Customer> removed = oldCustomers.stream()
.filter(c -> !newSet.contains(c))
.toList();
System.out.println(removed); // [Customer[id=1, name=Ann]]
Bei einer normalen Klasse überschreiben Sie beide Methoden, equals() und hashCode() – überschreiben Sie nur equals(), findet ein HashSet passende Objekte nicht.
Oft möchten Sie Objekte über einen Schlüssel statt über alle Felder vergleichen, damit ein geänderter Name nicht als anderer Kunde zählt:
Set<Integer> newIds = newCustomers.stream()
.map(Customer::id)
.collect(Collectors.toSet());
List<Customer> removedById = oldCustomers.stream()
.filter(c -> !newIds.contains(c.id()))
.toList();
System.out.println(removedById); // [Customer[id=1, name=Ann]]
7. Hilfsbibliotheken: Apache Commons und Guava
Nutzt Ihr Projekt bereits eine dieser Bibliotheken, erledigen sie Mengenoperationen in einer Zeile.
Apache Commons Collections (org.apache.commons.collections4.CollectionUtils):
Collection<String> onlyInA = CollectionUtils.subtract(listA, listB); // [apple, cherry]
Collection<String> inBoth = CollectionUtils.intersection(listA, listB); // banana, date (any order)
Collection<String> inOnlyOne = CollectionUtils.disjunction(listA, listB); // apple, cherry, fig (any order)
Guava (com.google.common.collect.Sets) arbeitet mit Sets und liefert Live-Ansichten:
Set<String> setA = new HashSet<>(listA);
Set<String> setB = new HashSet<>(listB);
Set<String> onlyInA = Sets.difference(setA, setB);
Set<String> inBoth = Sets.intersection(setA, setB);
Set<String> inOnlyOne = Sets.symmetricDifference(setA, setB);
CollectionUtils berücksichtigt, wie oft jedes Element vorkommt; Guavas Sets arbeiten mit unterschiedlichen Elementen. Kopieren Sie eine Guava-Ansicht mit new HashSet<>(view), wenn sich die zugrunde liegenden Sets ändern.
8. Performance: Keine List-Suche in einer Schleife
list.contains() prüft Element für Element. ArrayList.removeAll(otherList) und filter(otherList::contains) vergleichen also jedes Element der einen Liste mit jedem Element der anderen. Bei zwei Listen mit je 100.000 Einträgen können das bis zu 10 Milliarden Vergleiche sein.
Wenn Sie die andere Liste – wie in allen Beispielen oben – zuerst in ein HashSet packen, wird jede Abfrage zu einer Operation mit konstanter Laufzeit, und der gesamte Vergleich braucht rund 100.000 Schritte:
// Slow: O(n × m)
listA.stream().filter(listB::contains).toList();
// Fast: O(n + m)
Set<String> lookup = new HashSet<>(listB);
listA.stream().filter(lookup::contains).toList();
Zusammenfassung
| Ziel | Empfohlener Weg | Reihenfolge? | Hinweise |
|---|---|---|---|
| Gleich, gleiche Reihenfolge | a.equals(b) |
– | Funktioniert über List-Implementierungen hinweg |
| Gleich, beliebige Reihenfolge, mit Duplikaten | Sortierte Kopien oder Häufigkeits-Maps | – | Maps auch für nicht sortierbare Elemente |
| Gleich, beliebige Reihenfolge, ohne Duplikate | new HashSet<>(a).equals(new HashSet<>(b)) |
– | Ignoriert Wiederholungen |
| Gemeinsame Elemente | copy.retainAll(new HashSet<>(b)) |
Ja | Erst kopieren; List.of ist unveränderlich |
| Nur in A | copy.removeAll(new HashSet<>(b)) oder Stream-Filter |
Ja | Stream-Variante verändert nichts |
| In genau einer Liste | Stream.concat beider Differenzen |
Ja | Oder CollectionUtils.disjunction |
| Listen von Objekten | Records oder equals() + hashCode() |
Ja | Oder Vergleich über einen Schlüssel wie id |
Beginnen Sie mit einer HashSet-Abfrage und einem Stream-Filter – schnell, ohne Ihre Listen zu verändern, und passend für fast jeden Fall. Für eine schnelle Prüfung außerhalb Ihres Codes erledigt das Online-Tool zum Listenvergleich dasselbe im Browser. Sie arbeiten mit Python? Lesen Sie Zwei Listen in Python vergleichen.