liveBook · Manning

Map

As explained in 100 Java Mistakes and How to Avoid Them

HashMap is a Java map implementation that stores key-value associations using hashing.

A HashMap stores key-value associations using hashing. Its key-related failure mode is easy to miss: changing a key after insertion can make the entry impossible to find or remove. section 8.0 section 8.6

A mutable key becomes unreachable

A list is inserted as a set element and then modified. Its hash code changes, but the object remains in the bucket selected by its original hash code. A later contains or remove operation uses the new hash code, searches another bucket, and fails. section 8.6

Avoid mutable or equality-inconsistent keys:

  • Do not modify state affecting equals() or hashCode() while an object serves as a map key. section 8.6
  • Prefer immutable key types; LocalDateTime is identified as a modern alternative to mutable Date and Calendar objects. section 8.6
  • Treat StringBuilder as a risky key: its content-based compareTo() conflicts with identity-based equals() and hashCode(). section 8.6
  • Use static analysis as an aid, not a guarantee; a missed copy of a mutable BitSet caused items to disappear and produced incorrect warnings. section 8.6
Set<List<String>> set = new HashSet<>();
List<String> list = new ArrayList<>();
set.add(list);
list.add("Value");
System.out.println(set.contains(list));    #1

Inspect the minimal mutable-key example and observe that changing the list after insertion affects lookup behavior. section 8.6

public static void main(String[] args) {
  Set<StringBuilder> set = new HashSet<>();

  for (int count = 0; count < 40; count++) {     #1
    set.add(firstBucketStringBuilder());       
  }    
  int[] hashCodes = set.stream()     #2
        .mapToInt(Object::hashCode)    
        .sorted().toArray();   
  StringBuilder sb =    
    Stream.generate(StringBuilder::new)   
        .filter(b -> Arrays.binarySearch(  
              hashCodes, b.hashCode()) >= 0)    
        .findFirst().get();   
  set.add(sb);     #3
  System.out.println(set.contains(sb));     #4
  sb.append("b");     #5
  System.out.println(set.contains(sb));     #6
}

static StringBuilder firstBucketStringBuilder() {
  while (true) {
    StringBuilder sb = new StringBuilder("a");
    int hc = sb.hashCode();     #7
    if (((hc ^ (hc >>> 16)) & 0x3F) == 0) {   
      return sb;    
    }   
  }
}

Explore the StringBuilder case, including hash collisions, insertion, mutation, and subsequent contains checks. section 8.6

java -XX:+UnlockExperimentalVMOptions -XX:hashCode=2

Compare behavior under the configured hashCode option when investigating implementation-sensitive HashMap behavior. section 8.6

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Explains bucket selection, changed hash codes, equality inconsistencies, debugging consequences, and prevention choices.
Examples
Uses mutable lists, BitSet, Date, Calendar, LocalDateTime, and StringBuilder to expose key-related failures.
What it assumes
Assumes familiarity with Java collections and the equals(), hashCode(), and compareTo() contracts.
What you can do after
Diagnose failed lookups by checking key mutation, hashCode stability, equality consistency, and bucket-sensitive behavior.
How it connects
Connects HashMap with hashing, hashCode(), equals(), compareTo(), immutability, HashSet, and static analysis.

Also explained in

Compare how 42 books explain Map →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.