Von Elyndra Valen, Senior Entwicklerin bei Java Fleet Systems Consulting
Mit Einblicken von Nova Trent (Junior Dev) und Jamal Hassan (Backend Developer)

Schwierigkeit: 🟢 Einsteiger | 🟡 Mittel
Lesezeit: 30 Minuten
Voraussetzungen: Tag 1 (Listen) abgeschlossen, Java OOP (equals, Interfaces)
Kurs: Java Erweiterte Techniken – Tag 2 von 10


📖 Java Erweiterte Techniken – Alle Tage

TagThemaLevel
1Collections – Listen🟢
→ 2Collections – Sets & Maps🟢
3Generics🟡
4Lambda-Ausdrücke🟡
5Functional Interfaces🟡
6Stream-API🟡
7File I/O🟡
8Annotations & Multithreading Basics🟡
9Multithreading – Synchronisation🔴
10Netzwerkprogrammierung🔴

📍 Du bist hier: Tag 2


⚡ Das Wichtigste in 30 Sekunden

Dein Problem: Du hast eine Liste von Benutzern und willst Duplikate vermeiden. Oder du brauchst schnellen Zugriff auf Daten per Schlüssel – nicht per Index.

Die Lösung:

  • Set – eine Collection, die automatisch Duplikate verhindert
  • Map – Key-Value-Paare mit blitzschnellem Zugriff über den Key

Heute lernst du:

  • ✅ Wann du Set statt List verwendest
  • ✅ HashSet vs. TreeSet vs. LinkedHashSet – und wann welches
  • ✅ HashMap als dein neues Lieblings-Tool für Key-Value-Daten
  • Das Geheimnis von equals() und hashCode() – ohne das Sets und Maps nicht funktionieren!
  • ✅ Comparable und Comparator für sortierte Collections

Für wen ist dieser Artikel?

  • 🌱 Anfänger: Du lernst Sets und Maps von Grund auf
  • 🌿 Erfahrene: Du verstehst endlich den equals/hashCode-Vertrag
  • 🌳 Profis: TreeSet mit Custom Comparator, NavigableMap-Features

Zeit-Investment: 30 Minuten Lesen + 45-90 Minuten Praxis


👋 Elyndra: „Nova, ich bin stolz auf dich.“

Hi! 👋

Elyndra hier, zurück für Tag 2. Gestern haben wir Listen gemeistert. Heute wird’s spannender.

Letzte Woche hab ich was Schönes beobachtet: Tom, unser Werkstudent, kam zu Nova – komplett frustriert:

Set<Person> personen = new HashSet<>();
personen.add(new Person("Max", "Müller"));
personen.add(new Person("Max", "Müller"));  // Duplikat!
System.out.println(personen.size());  // Erwartung: 1

Ausgabe: 2

Tom: „Nova, mein Set ist kaputt! Da sind zwei Max Müller drin!“

Nova hat sich den Code angeschaut – und ich hab gesehen, wie es bei ihr klick gemacht hat. Sie wusste sofort, was los ist. Vor einem Jahr hätte sie noch genauso ratlos dagestanden wie Tom jetzt.

„Tom, zeig mal deine Person-Klasse… Ah. Du hast equals() und hashCode() nicht überschrieben.“

Der Klassiker. Den Fehler macht jeder einmal.

Heute zeige ich dir, wie du diesen Fehler vermeidest – und warum das Verständnis von equals() und hashCode() der Schlüssel zu Sets und Maps ist.

💡 Du kennst Set/Map schon, willst aber equals/hashCode verstehen?
→ Spring direkt zu „Der equals/hashCode-Vertrag“


🖼️ Das Konzept auf einen Blick

Sets

Abbildung 1: Set speichert einzigartige Elemente, Map speichert Key-Value-Paare


🟢 GRUNDLAGEN

Was ist ein Set?

Ein Set ist eine Collection, die keine Duplikate erlaubt. Wenn du versuchst, ein Element hinzuzufügen, das schon existiert, passiert einfach… nichts.

Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("programming");
tags.add("java");  // Duplikat – wird ignoriert!

System.out.println(tags.size());  // 2
System.out.println(tags);         // [java, programming]

Wann brauchst du ein Set?

SituationList oder Set?
Einkaufsliste (Reihenfolge wichtig)List
Unique Tags für einen ArtikelSet
Playlist (gleicher Song mehrfach möglich)List
Bereits besuchte URLs (keine Duplikate)Set
Benutzer-IDs die online sindSet

💬 Nova fragt: „Aber woher weiß das Set, ob etwas ein Duplikat ist?“

Gute Frage! Bei Strings und primitiven Wrappern (Integer, etc.) funktioniert es automatisch. Bei eigenen Klassen musst du Java sagen, wann zwei Objekte „gleich“ sind – mit equals() und hashCode(). Dazu kommen wir gleich!


Die drei Set-Implementierungen

Abbildung 2: HashSet, LinkedHashSet und TreeSet im Vergleich

1. HashSet – Der Schnelle (Standard)

Set<String> hashSet = new HashSet<>();
hashSet.add("Banane");
hashSet.add("Apfel");
hashSet.add("Orange");

System.out.println(hashSet);  // [Banane, Orange, Apfel] – KEINE garantierte Reihenfolge!

Eigenschaften:

  • ✅ Schnellste Implementierung: O(1) für add, remove, contains
  • ✅ Dein Standard-Set für die meisten Fälle
  • ❌ Keine garantierte Reihenfolge

2. LinkedHashSet – Der mit Gedächtnis

Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Banane");
linkedHashSet.add("Apfel");
linkedHashSet.add("Orange");

System.out.println(linkedHashSet);  // [Banane, Apfel, Orange] – Einfüge-Reihenfolge!

Eigenschaften:

  • ✅ Behält die Einfüge-Reihenfolge
  • ✅ Fast so schnell wie HashSet
  • ❌ Etwas mehr Speicherverbrauch

3. TreeSet – Der Sortierte

Set<String> treeSet = new TreeSet<>();
treeSet.add("Banane");
treeSet.add("Apfel");
treeSet.add("Orange");

System.out.println(treeSet);  // [Apfel, Banane, Orange] – Alphabetisch sortiert!

Eigenschaften:

  • ✅ Automatisch sortiert
  • ✅ Bietet zusätzliche Methoden: first(), last(), headSet(), tailSet()
  • ❌ Langsamer: O(log n) statt O(1)
  • ⚠️ Elemente müssen Comparable sein (oder du gibst einen Comparator)

Was ist eine Map?

Eine Map speichert Key-Value-Paare. Du greifst auf Werte über ihren Schlüssel zu – nicht über einen Index.

Map<String, Integer> alter = new HashMap<>();
alter.put("Max", 25);
alter.put("Anna", 30);
alter.put("Tom", 22);

System.out.println(alter.get("Anna"));  // 30
System.out.println(alter.get("Lisa"));  // null (nicht vorhanden)

Wichtig: Keys müssen eindeutig sein! Wenn du denselben Key nochmal verwendest, wird der alte Wert überschrieben:

alter.put("Max", 25);
alter.put("Max", 26);  // Überschreibt den alten Wert!
System.out.println(alter.get("Max"));  // 26

Wann brauchst du eine Map?

SituationDatenstruktur
Telefonbuch: Name → NummerMap
Konfiguration: Key → ValueMap
Wortfrequenz zählenMap
HTTP-HeaderMap
Cache: ID → ObjektMap

Die drei Map-Implementierungen

ImplementierungReihenfolgePerformanceWann verwenden?
HashMapKeineO(1)Standard – 90% der Fälle
LinkedHashMapEinfüge-ReihenfolgeO(1)Wenn Reihenfolge wichtig
TreeMapSortiert nach KeyO(log n)Wenn sortiert nach Key nötig
// HashMap – keine garantierte Reihenfolge
Map<String, Integer> hashMap = new HashMap<>();

// LinkedHashMap – behält Einfüge-Reihenfolge
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();

// TreeMap – sortiert nach Key
Map<String, Integer> treeMap = new TreeMap<>();

Wichtige Map-Methoden

Map<String, Integer> scores = new HashMap<>();

// Hinzufügen
scores.put("Alice", 100);
scores.put("Bob", 85);

// Abrufen
int aliceScore = scores.get("Alice");           // 100
int unknownScore = scores.getOrDefault("Eve", 0); // 0 (statt null)

// Prüfen
boolean hasAlice = scores.containsKey("Alice");   // true
boolean has100 = scores.containsValue(100);       // true

// Entfernen
scores.remove("Bob");

// Alle Keys, alle Values, alle Einträge
Set<String> namen = scores.keySet();
Collection<Integer> punkte = scores.values();
Set<Map.Entry<String, Integer>> entries = scores.entrySet();

// Durch Map iterieren
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// Oder mit forEach (Java 8+)
scores.forEach((name, score) -> System.out.println(name + ": " + score));

💬 Nova: „Das getOrDefault() ist nice! Kein null-Check mehr nötig.“

Genau! Und es gibt noch mehr solcher Convenience-Methoden. Dazu kommen wir in der Professionals-Sektion.


🟡 PROFESSIONALS

Der equals/hashCode-Vertrag

Das ist DER wichtigste Abschnitt dieses Artikels. Wenn du nur eine Sache mitnimmst, dann diese.

💬 Jamal: „Real talk: Ich hab in Code Reviews mehr Bugs durch falsches equals/hashCode gesehen als durch fast alles andere. Das hier ist kein Nice-to-know – das ist essential.“

Das Problem (nochmal)

public class Person {
    private String vorname;
    private String nachname;
    
    public Person(String vorname, String nachname) {
        this.vorname = vorname;
        this.nachname = nachname;
    }
    
    // KEIN equals() und hashCode() überschrieben!
}
Set<Person> personen = new HashSet<>();
personen.add(new Person("Max", "Müller"));
personen.add(new Person("Max", "Müller"));

System.out.println(personen.size());  // 2 – FALSCH!

Warum passiert das?

Ohne überschriebenes equals() verwendet Java die Standardimplementierung von Object: Zwei Objekte sind nur gleich, wenn sie dieselbe Referenz haben (also dasselbe Objekt im Speicher sind).

Person p1 = new Person("Max", "Müller");
Person p2 = new Person("Max", "Müller");

System.out.println(p1 == p2);      // false – verschiedene Objekte
System.out.println(p1.equals(p2)); // false – ohne Override: wie ==

Die Lösung: equals() und hashCode() überschreiben

public class Person {
    private String vorname;
    private String nachname;
    
    public Person(String vorname, String nachname) {
        this.vorname = vorname;
        this.nachname = nachname;
    }
    
    @Override
    public boolean equals(Object o) {
        // 1. Identität: Bin ich dasselbe Objekt?
        if (this == o) return true;
        
        // 2. Null-Check und Typ-Check
        if (o == null || getClass() != o.getClass()) return false;
        
        // 3. Cast und Feldvergleich
        Person person = (Person) o;
        return Objects.equals(vorname, person.vorname) 
            && Objects.equals(nachname, person.nachname);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(vorname, nachname);
    }
}

Jetzt funktioniert es:

Set<Person> personen = new HashSet<>();
personen.add(new Person("Max", "Müller"));
personen.add(new Person("Max", "Müller"));

System.out.println(personen.size());  // 1 – RICHTIG!

Der Vertrag (die Regeln)

Diese Regeln MÜSSEN eingehalten werden:

  1. Konsistenz: equals() muss bei gleichen Werten immer dasselbe Ergebnis liefern
  2. Symmetrie: Wenn a.equals(b), dann muss auch b.equals(a) gelten
  3. Transitivität: Wenn a.equals(b) und b.equals(c), dann muss a.equals(c) gelten
  4. Null-Sicherheit: a.equals(null) muss immer false sein
  5. hashCode-Konsistenz: Wenn a.equals(b), dann MUSS a.hashCode() == b.hashCode() sein!

⚠️ Die goldene Regel:
Wenn du equals() überschreibst, MUSST du auch hashCode() überschreiben!

Warum? HashSet und HashMap verwenden hashCode() um zu entscheiden, in welchen „Bucket“ ein Element gehört. Erst danach wird equals() aufgerufen.

Abbildung 3: Wie HashSet Elemente speichert und findet

Wenn zwei gleiche Objekte unterschiedliche hashCodes haben, landen sie in verschiedenen Buckets – und das Set findet sie nie als Duplikate!


Best Practices für equals/hashCode

1. Nutze Objects.equals() und Objects.hash():

// ✅ Sicher und lesbar
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return Objects.equals(vorname, person.vorname) 
        && Objects.equals(nachname, person.nachname);
}

@Override
public int hashCode() {
    return Objects.hash(vorname, nachname);
}

2. Lass die IDE es generieren:

In IntelliJ: Alt + Insert → „equals() and hashCode()“ In NetBeans: Alt + Insert → „equals() and hashCode()…“ In Eclipse: Source → „Generate hashCode() and equals()“

3. Verwende dieselben Felder in beiden Methoden:

// ❌ FALSCH: unterschiedliche Felder
public boolean equals(Object o) {
    // vergleicht vorname UND nachname
}
public int hashCode() {
    return Objects.hash(vorname);  // nur vorname!
}

// ✅ RICHTIG: gleiche Felder
public boolean equals(Object o) {
    // vergleicht vorname UND nachname
}
public int hashCode() {
    return Objects.hash(vorname, nachname);  // beide!
}

📚 Auffrischung nötig?
→ Java OOP Kurs, Tag 3: Datenkapselung (Getter/Setter, Object-Methoden)


Comparable und Comparator

Für TreeSet und TreeMap müssen Elemente sortierbar sein. Dafür gibt es zwei Wege:

1. Comparable – „Ich weiß selbst, wie ich sortiert werde“

Implementiere Comparable<T> in deiner Klasse:

public class Person implements Comparable<Person> {
    private String vorname;
    private String nachname;
    
    // ... Constructor, equals, hashCode ...
    
    @Override
    public int compareTo(Person other) {
        // Erst nach Nachname, dann nach Vorname
        int nachnameCompare = this.nachname.compareTo(other.nachname);
        if (nachnameCompare != 0) {
            return nachnameCompare;
        }
        return this.vorname.compareTo(other.vorname);
    }
}
Set<Person> sortiertePersonen = new TreeSet<>();
sortiertePersonen.add(new Person("Max", "Müller"));
sortiertePersonen.add(new Person("Anna", "Schmidt"));
sortiertePersonen.add(new Person("Tom", "Müller"));

// Ausgabe: [Max Müller, Tom Müller, Anna Schmidt]
// (Erst alle Müller, dann Schmidt – alphabetisch)

Rückgabewert von compareTo:

  • < 0: this kommt VOR other
  • 0: gleich
  • > 0: this kommt NACH other

2. Comparator – „Externe Sortierlogik“

Wenn du die Klasse nicht ändern kannst oder verschiedene Sortierungen brauchst:

// Nach Vorname sortieren
Comparator<Person> nachVorname = (p1, p2) -> p1.getVorname().compareTo(p2.getVorname());

Set<Person> nachVornameSortiert = new TreeSet<>(nachVorname);

// Oder mit Comparator-Factory-Methoden (eleganter):
Comparator<Person> nachNachnameDannVorname = 
    Comparator.comparing(Person::getNachname)
              .thenComparing(Person::getVorname);

Set<Person> sortiert = new TreeSet<>(nachNachnameDannVorname);

💬 Nova: „Wann Comparable, wann Comparator?“

Faustregeln:

  • Comparable: Wenn es EINE natürliche Sortierung gibt (z.B. Datum chronologisch)
  • Comparator: Wenn du mehrere Sortierungen brauchst oder die Klasse nicht ändern kannst

Häufige Stolperfallen

1. Mutable Keys in HashMap

// ❌ GEFÄHRLICH!
List<String> key = new ArrayList<>();
key.add("A");

Map<List<String>, String> map = new HashMap<>();
map.put(key, "Wert");

System.out.println(map.get(key));  // "Wert"

key.add("B");  // Key verändert!
System.out.println(map.get(key));  // null – VERLOREN!

Problem: Wenn du den Key veränderst, ändert sich sein hashCode. Die Map findet ihn nicht mehr.

Lösung: Verwende nur immutable Keys (String, Integer, etc.) oder ändere Keys nie nach dem Einfügen.


2. put() vs. putIfAbsent()

Map<String, Integer> counts = new HashMap<>();

// ❌ Überschreibt existierenden Wert
counts.put("java", 1);
counts.put("java", 2);  // Überschreibt!

// ✅ Nur einfügen wenn nicht vorhanden
counts.putIfAbsent("python", 1);
counts.putIfAbsent("python", 2);  // Wird ignoriert

3. computeIfAbsent() für komplexe Initialisierung

// Szenario: Map von Listen aufbauen
Map<String, List<String>> gruppiert = new HashMap<>();

// ❌ Umständlich
String key = "gruppe1";
if (!gruppiert.containsKey(key)) {
    gruppiert.put(key, new ArrayList<>());
}
gruppiert.get(key).add("Element");

// ✅ Elegant mit computeIfAbsent
gruppiert.computeIfAbsent(key, k -> new ArrayList<>()).add("Element");

🔵 BONUS

NavigableSet und NavigableMap

TreeSet und TreeMap implementieren NavigableSet/NavigableMap mit zusätzlichen Methoden:

TreeSet<Integer> zahlen = new TreeSet<>(List.of(1, 3, 5, 7, 9));

// Nächstkleinerer Wert
System.out.println(zahlen.lower(5));   // 3
System.out.println(zahlen.floor(5));   // 5 (inklusive)

// Nächstgrößerer Wert
System.out.println(zahlen.higher(5));  // 7
System.out.println(zahlen.ceiling(5)); // 5 (inklusive)

// Teilmengen
System.out.println(zahlen.headSet(5));        // [1, 3]
System.out.println(zahlen.tailSet(5));        // [5, 7, 9]
System.out.println(zahlen.subSet(3, 7));      // [3, 5]

// Erstes/Letztes entfernen
System.out.println(zahlen.pollFirst());  // 1 (entfernt)
System.out.println(zahlen.pollLast());   // 9 (entfernt)

Performance-Vergleich

OperationHashSetTreeSetLinkedHashSet
add()O(1)O(log n)O(1)
remove()O(1)O(log n)O(1)
contains()O(1)O(log n)O(1)
IterationO(n)O(n)O(n)
SpeicherMittelHochHoch
OperationHashMapTreeMapLinkedHashMap
put()O(1)O(log n)O(1)
get()O(1)O(log n)O(1)
remove()O(1)O(log n)O(1)
containsKey()O(1)O(log n)O(1)

💬 Jamal: „In 95% der Fälle ist HashMap die richtige Wahl. TreeMap nur wenn du wirklich sortierte Keys brauchst – und das ist seltener als man denkt.“


EnumSet und EnumMap

Für Enums gibt es spezialisierte, hochperformante Implementierungen:

enum Wochentag { MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, SAMSTAG, SONNTAG }

// EnumSet – extrem schnell für Enum-Elemente
Set<Wochentag> arbeitstage = EnumSet.range(Wochentag.MONTAG, Wochentag.FREITAG);
Set<Wochentag> wochenende = EnumSet.of(Wochentag.SAMSTAG, Wochentag.SONNTAG);
Set<Wochentag> alleAusserMontag = EnumSet.complementOf(EnumSet.of(Wochentag.MONTAG));

// EnumMap – schnellste Map wenn Key ein Enum ist
Map<Wochentag, String> aktivitaeten = new EnumMap<>(Wochentag.class);
aktivitaeten.put(Wochentag.MONTAG, "Kaffee trinken");
aktivitaeten.put(Wochentag.FREITAG, "Feierabend feiern");

Warum sind die so schnell? Sie nutzen intern Bit-Operationen und Arrays statt Hash-Tabellen.


Immutable Sets und Maps (Java 9+)

// Immutable Set
Set<String> immutableSet = Set.of("A", "B", "C");

// Immutable Map
Map<String, Integer> immutableMap = Map.of(
    "eins", 1,
    "zwei", 2,
    "drei", 3
);

// Für mehr als 10 Einträge:
Map<String, Integer> grosseMap = Map.ofEntries(
    Map.entry("eins", 1),
    Map.entry("zwei", 2),
    // ... beliebig viele
);

// Kopie einer bestehenden Map (immutable)
Map<String, Integer> kopie = Map.copyOf(existierendeMap);

⚠️ Achtung: Set.of() und Map.of() erlauben keine null-Werte und keine Duplikate bei Keys!


💬 Real Talk: Die Klassiker-Falle

Java Fleet Büro, Freitag 16:45. Tom starrt frustriert auf seinen Bildschirm.


Tom: „Nova, ich dreh durch! Mein Set findet meine Objekte nicht. Ich füge eine Person hinzu und contains() sagt false!“

Nova: schaut auf den Code „Zeig mal deine Person-Klasse… Ah, da ist es. Du hast equals überschrieben, aber nicht hashCode.“

Tom: „Aber ich vergleiche doch mit equals? Warum braucht das Set hashCode?“

Jamal: rollt mit seinem Stuhl rüber „HashSet heißt HASH-Set. Der hashCode entscheidet, in welchem Bucket gesucht wird. Ohne passenden hashCode guckt Java im falschen Bucket – und findet nichts.“

Nova: „Stell dir vor, du suchst in einer Bibliothek ein Buch. Der hashCode ist wie die Regalnummer. Wenn zwei Bücher ‚gleich‘ sind aber verschiedene Regalnummern haben, findest du das Duplikat nie.“

Tom: „Ohhh! Also muss ich IMMER beide überschreiben?“

Jamal: „Immer. Keine Ausnahme. Lass die IDE das generieren – dann kann nichts schiefgehen.“

Tom: tippt kurz „Alt+Insert… equals und hashCode… fertig. Wow, das funktioniert ja jetzt!“

Elyndra: hat vom Nachbartisch zugehört, lächelt „Nova, gut erklärt. Du bist weit gekommen.“

Nova: grinst „Ich hatte eine gute Lehrerin.“


❓ FAQ

Frage 1: Wann nehme ich Set, wann List?

Set wenn:

  • Keine Duplikate erlaubt
  • Reihenfolge egal (oder sortiert mit TreeSet)
  • Schnelle contains()-Prüfung wichtig

List wenn:

  • Duplikate möglich/gewünscht
  • Index-Zugriff nötig
  • Reihenfolge wichtig

Frage 2: HashMap oder TreeMap?

HashMap (Standard): Wenn du O(1)-Zugriff brauchst und Sortierung egal ist.

TreeMap: Wenn du nach Keys sortiert iterieren musst oder Range-Queries brauchst (subMap, headMap, etc.).


Frage 3: Kann ich null in Sets/Maps speichern?

Collectionnull Keynull Values
HashSet✅ Ein null
TreeSet❌ Nein
HashMap✅ Ein null✅ Beliebig viele
TreeMap❌ Nein✅ Ja
Set.of()❌ Nein
Map.of()❌ Nein❌ Nein

Frage 4: Muss ich bei Records auch equals/hashCode überschreiben?

Nein! Records (ab Java 16) generieren automatisch equals(), hashCode() und toString() basierend auf allen Feldern:

record Person(String vorname, String nachname) {}

// equals und hashCode funktionieren automatisch!
Set<Person> personen = new HashSet<>();
personen.add(new Person("Max", "Müller"));
personen.add(new Person("Max", "Müller"));
System.out.println(personen.size());  // 1 – Korrekt!

Frage 5: Was ist der Unterschied zwischen keySet() und entrySet()?

Map<String, Integer> map = Map.of("A", 1, "B", 2);

// keySet() – nur die Keys
for (String key : map.keySet()) {
    Integer value = map.get(key);  // Extra Lookup nötig!
}

// entrySet() – Key UND Value zusammen (effizienter!)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();  // Kein extra Lookup!
}

💡 Tipp: Wenn du Key UND Value brauchst, nimm entrySet() – das ist schneller.


Frage 6: Wie zähle ich Vorkommen von Elementen?

List<String> woerter = List.of("java", "python", "java", "kotlin", "java");

// Mit Map manuell
Map<String, Integer> counts = new HashMap<>();
for (String wort : woerter) {
    counts.merge(wort, 1, Integer::sum);
}
// {java=3, python=1, kotlin=1}

// Oder mit Streams (Tag 6)
Map<String, Long> counts2 = woerter.stream()
    .collect(Collectors.groupingBy(w -> w, Collectors.counting()));

Frage 7: Bernd hat mir gesagt, Hashtable sei sicherer als HashMap?

seufz Bernd meint es gut, aber Hashtable ist Legacy aus Java 1.0. Ja, es ist synchronized – aber das macht es langsamer, nicht „sicherer“.

Für Thread-sichere Maps:

  • ConcurrentHashMap (moderne Lösung)
  • Collections.synchronizedMap(new HashMap<>()) (wenn’s sein muss)

Nicht verwenden: Hashtable, Vector, Stack – das ist Java-Geschichte, kein modernes Java.

🔍 Psst… suchst du „behind the code“ oder „in my feels“? Die Private Logs der Crew findest du, wenn du weißt wo du suchen musst…


Frage 8: Wie kopiere ich ein Set/eine Map?

Set<String> original = new HashSet<>(Set.of("A", "B", "C"));

// Shallow Copy (gleiche Objektreferenzen)
Set<String> kopie1 = new HashSet<>(original);

// Immutable Copy (Java 10+)
Set<String> kopie2 = Set.copyOf(original);

// Für Map:
Map<String, Integer> originalMap = new HashMap<>();
Map<String, Integer> kopieMap = new HashMap<>(originalMap);
Map<String, Integer> immutableKopie = Map.copyOf(originalMap);

📚 Quiz-Lösungen

Die Fragen findest du in der Challenge-Sektion.

Frage 1: 2 – Ohne equals/hashCode-Override sind zwei new Person() verschiedene Objekte.

Frage 2: TreeSet, weil es automatisch sortiert. HashSet hat keine garantierte Reihenfolge.

Frage 3: null – Der Key wurde nach dem put() verändert, sein hashCode ist jetzt anders.

Frage 4: O(1) für HashMap, O(log n) für TreeMap.

Frage 5: Ja! Wenn equals true zurückgibt, MÜSSEN die hashCodes gleich sein.


🎁 Cheat Sheet

🟢 Basics (Zum Nachschlagen)

// Set erstellen
Set<String> set = new HashSet<>();       // Standard
Set<String> ordered = new LinkedHashSet<>();  // Reihenfolge
Set<String> sorted = new TreeSet<>();    // Sortiert

// Set-Operationen
set.add("element");
set.remove("element");
set.contains("element");
set.size();
set.isEmpty();
set.clear();

// Map erstellen
Map<String, Integer> map = new HashMap<>();
Map<String, Integer> ordered = new LinkedHashMap<>();
Map<String, Integer> sorted = new TreeMap<>();

// Map-Operationen
map.put("key", value);
map.get("key");
map.getOrDefault("key", defaultValue);
map.remove("key");
map.containsKey("key");
map.containsValue(value);
map.keySet();
map.values();
map.entrySet();

🟡 Patterns (Für den Alltag)

// equals/hashCode Pattern
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    MyClass that = (MyClass) o;
    return Objects.equals(field1, that.field1) 
        && Objects.equals(field2, that.field2);
}

@Override
public int hashCode() {
    return Objects.hash(field1, field2);
}

// Map mit Default-Wert initialisieren
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

// Elemente zählen
map.merge(key, 1, Integer::sum);

// Durch Map iterieren
map.forEach((k, v) -> System.out.println(k + ": " + v));

🔵 Advanced (Für Profis)

// Comparator-Chain
Comparator<Person> comparator = Comparator
    .comparing(Person::getNachname)
    .thenComparing(Person::getVorname)
    .reversed();

// NavigableSet-Features
TreeSet<Integer> zahlen = new TreeSet<>();
zahlen.lower(5);     // Nächstkleinerer
zahlen.higher(5);    // Nächstgrößerer
zahlen.subSet(3, 7); // Range [3, 7)

// EnumSet/EnumMap
Set<Day> workDays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
Map<Day, String> schedule = new EnumMap<>(Day.class);

🎨 Challenge für dich!

Teste dein Wissen!

Quiz-Fragen:

  1. Was gibt personen.size() aus, wenn Person KEIN equals/hashCode hat? Set<Person> personen = new HashSet<>(); personen.add(new Person("Max", "Müller")); personen.add(new Person("Max", "Müller"));
  2. Welches Set behält die Sortierung: HashSet, LinkedHashSet oder TreeSet?
  3. Was gibt dieser Code aus? List<String> key = new ArrayList<>(); key.add("A"); Map<List<String>, String> map = new HashMap<>(); map.put(key, "Wert"); key.add("B"); System.out.println(map.get(key));
  4. Was ist die Zeitkomplexität von get() bei HashMap vs. TreeMap?
  5. Müssen zwei Objekte mit gleichem hashCode auch equals sein?

Praktische Aufgaben:

🟢 Level 1 – Einsteiger

  • [ ] Erstelle ein HashSet mit 5 Städtenamen und prüfe ob „Berlin“ enthalten ist
  • [ ] Erstelle eine HashMap die Ländernamen auf ihre Hauptstädte mappt Geschätzte Zeit: 15-30 Minuten

🟡 Level 2 – Fortgeschritten

  • [ ] Implementiere eine Student-Klasse mit korrektem equals/hashCode
  • [ ] Zähle die Worthäufigkeit in einem Text mit HashMap Geschätzte Zeit: 30-60 Minuten

🔵 Level 3 – Profi

  • [ ] Implementiere einen LRU-Cache mit LinkedHashMap
  • [ ] Erstelle ein TreeSet mit Comparator für Personen (erst Alter, dann Name) Geschätzte Zeit: 1-2 Stunden

📦 Downloads

Alle Code-Beispiele zum Herunterladen:

ProjektFür wen?Download
tag02-sets-maps-starter.zip🟢 Einsteiger⬇️ Download
tag02-sets-maps-complete.zip🟡 Alle Levels⬇️ Download

Quick Start:

# 1. ZIP entpacken
unzip tag02-sets-maps-starter.zip

# 2. In NetBeans öffnen
# Datei → Projekt öffnen → Ordner auswählen

# 3. Main.java ausführen
# Rechtsklick → Run File

🔗 Weiterführende Links

🇩🇪 Deutsch

RessourceBeschreibung
Rheinwerk OpenBook: Java SE 8Kostenloses Standardwerk, Kapitel Datenstrukturen
Javabeginners: HashMapGrundlagen HashMap/Hashtable
straub.as: HashSet erklärtBucket-Prinzip auf Deutsch erklärt
DelftStack: HashMap vs HashSetUnterschiede mit Codebeispielen

🇬🇧 Englisch

RessourceBeschreibungLevel
Oracle Java Tutorials: CollectionsOffizielle Dokumentation🟢
W3Schools: Java HashSetInteraktive Beispiele🟢
GeeksforGeeks: HashSetUmfassende Referenz🟡
Baeldung: equals() and hashCode()DER Artikel zum Thema!🟡
Baeldung: Guide to hashCode()Tiefes Verständnis🟡
HowToDoInJava: hashCode & equalsBest Practices🟡
Baeldung: Records equals/hashCodeModern: Records ab Java 16🔵

🛠️ Tools

ToolBeschreibung
EqualsVerifierAutomatisches Testen von equals/hashCode
IntelliJ: Generate equals/hashCodeIDE-Generator

📧 Offizielle Dokumentation


👋 Geschafft! 🎉

Was du heute gelernt hast:

✅ Set für einzigartige Elemente – HashSet, TreeSet, LinkedHashSet
✅ Map für Key-Value-Paare – HashMap, TreeMap, LinkedHashMap
Der equals/hashCode-Vertrag – das Fundament für Collections mit eigenen Klassen
✅ Comparable vs. Comparator für Sortierung
✅ Moderne Convenience-Methoden: getOrDefault, computeIfAbsent, merge

Egal ob du heute zum ersten Mal von equals/hashCode gehört hast oder endlich verstanden hast, warum dein Code nicht funktioniert hat – du hast etwas Wichtiges gelernt. Das zählt!

Fragen? Schreib uns:

  • elyndra.valen@java-developer.online
  • nova.trent@java-developer.online
  • jamal.hassan@java-developer.online

📖 Weiter geht’s!

← Vorheriger Tag: Tag 1: Collections – Listen
→ Nächster Tag: Tag 3: Generics

↑ Zurück zur Kursübersicht


Tags: #Java #Collections #Set #Map #HashSet #HashMap #equals #hashCode

© 2025 Java Fleet Systems Consulting | java-developer.online

Autoren

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.

  • Ensign Nova Trent

    24 Jahre alt, frisch von der Universität als Junior Entwicklerin bei Java Fleet Systems Consulting. Nova ist brilliant in Algorithmen und Datenstrukturen, aber neu in der praktischen Java-Enterprise-Entwicklung. Sie brennt darauf, ihre ersten echten Projekte zu bauen und entdeckt dabei die Lücke zwischen Uni-Theorie und Entwickler-Realität. Sie liebt Star Treck das ist der Grund warum alle Sie Ensign Nova nennen und arbeitet daraufhin das sie Ihren ersten Knopf am Kragen bekommt.

  • Jamal Hassan

    ⚙️ Jamal Hassan – Der Zuverlässige

    Backend Developer | 34 Jahre | „Ich schau mir das an.“

    Wenn im Team jemand gebraucht wird, der ruhig bleibt, während alle anderen hektisch diskutieren, dann ist es Jamal.
    Er redet nicht viel – er löst.
    Er plant wie ein Schachspieler: drei Züge im Voraus, jede Entscheidung mit Folgen bedacht.
    Seine Art zu arbeiten ist kein Sprint, sondern eine Strategie.

    Er kam 2021 zur Java Fleet, nachdem sein vorheriges Startup gescheitert war. Statt Frust hat er Gelassenheit mitgebracht – und eine Haltung, die das Team bis heute prägt: Stabilität ist keine Bremse, sondern ein Fundament.
    In einer Welt voller Hypes baut Jamal Systeme, die bleiben.

    💻 Die Tech-Seite

    Jamal ist der Inbegriff von Backend-Handwerk.
    Er liebt Architektur, die logisch ist, Datenmodelle, die Bestand haben, und Services, die einfach laufen.
    Spring Boot, REST, Kafka, Docker, DDD – das sind seine Werkzeuge, aber nicht sein Selbstverständnis.
    Er versteht Systeme als Ökosysteme: Jede Entscheidung hat Auswirkungen, jedes Modul muss sich in das Ganze einfügen.

    Er ist der Typ Entwickler, der eine halbe Stunde in Stille auf den Bildschirm schaut – und dann mit einem Satz alles löst:

    „Das Problem liegt nicht im Code. Es liegt in der Annahme.“

    Sein Code ist wie seine Persönlichkeit: still, präzise, verlässlich.
    Er dokumentiert, was nötig ist, und schreibt Tests, weil er Verantwortung ernst nimmt.
    Er hält nichts von Schnellschüssen – und noch weniger von Ausreden.

    🌿 Die menschliche Seite

    Jamal ist kein Mensch der Bühne.
    Er mag es, wenn andere glänzen – Hauptsache, das System läuft.
    Er trinkt arabischen Kaffee, spielt Schach im Verein und genießt es, wenn Dinge logisch ineinandergreifen – egal ob Code oder Leben.
    In der Kaffeeküche hört man ihn selten, aber wenn er etwas sagt, ist es meist ein Satz, der hängen bleibt.

    Im Team ist er der stille Vertraute, der Probleme anhört, bevor er sie bewertet.
    Nova nennt ihn „den Debugger in Menschengestalt“, Kat sagt: „Wenn Jamal nickt, weißt du, dass du auf der richtigen Spur bist.“
    Und Cassian beschreibt ihn als „Architekt mit Geduld und ohne Ego“.

    🧠 Seine Rolle im Team

    Jamal ist das strukturelle Rückgrat der Crew.
    Er denkt in Systemen, nicht in Features – in Verantwortlichkeiten, nicht in Ruhm.
    Wenn Projekte drohen, aus dem Ruder zu laufen, bringt er sie mit wenigen Worten und einer klaren Architektur wieder auf Kurs.
    Er ist das, was Franz-Martin „den ruhigen Hafen im Sturm“ nennt.

    ⚡ Superkraft

    Stabilität durch Denken.
    Jamal löst nicht nur technische Probleme – er beseitigt deren Ursachen.

    ☕ Motto

    „Ich schau mir das an.“
    (Und wenn er das sagt, ist das Problem so gut wie gelöst.)