Companion code for the ankurm.com post "Top 40 Java Collections Interview Questions (HashMap Internals to Fail-Fast Iterators)". Verified against JDK 25's own HashMap source (TREEIFY_THRESHOLD=8, UNTREEIFY_THRESHOLD=6, MIN_TREEIFY_CAPACITY=64), with every claim backed by a committed docs/output/*.txt transcript from a real run. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YXCrLgRKFgCh9RHKW8xaqJ
115 lines
5.8 KiB
Markdown
115 lines
5.8 KiB
Markdown
# java-interview-lab
|
|
|
|
Runnable companion code for ankurm.com's Java interview-prep article series. Every number,
|
|
stack trace, and console line quoted in those articles comes from a file committed somewhere
|
|
under this repository — nothing is retyped or paraphrased from memory.
|
|
|
|
One module per article. This repository currently holds:
|
|
|
|
| Module | Article | Status |
|
|
|---|---|---|
|
|
| [`collections`](collections/) | Top 40 Java Collections Interview Questions (HashMap Internals to Fail-Fast Iterators) | this commit |
|
|
|
|
## `collections` module
|
|
|
|
HashMap/HashSet/ArrayList internals, verified against the real JDK 25 `java.util.HashMap`
|
|
source (not against its javadoc prose) and reproduced with real, runnable code.
|
|
|
|
### Versions
|
|
|
|
| Component | Version | Verified against |
|
|
|---|---|---|
|
|
| JDK | **25.0.4.1+1** (Temurin, LTS) | `java -version`; `java.util.HashMap` source extracted from this JDK's own `lib/src.zip` |
|
|
| JUnit | **5.11.0** | `org.junit.jupiter:junit-jupiter` on Maven Central |
|
|
| Maven | 3.9.11 | `mvn -version` |
|
|
|
|
The three constants this article leans on hardest (`TREEIFY_THRESHOLD = 8`,
|
|
`UNTREEIFY_THRESHOLD = 6`, `MIN_TREEIFY_CAPACITY = 64`) were read directly out of
|
|
`java.util.HashMap`'s source for this exact JDK build, not copied from a blog post or an older
|
|
javadoc. See `collections/src/main/java/.../BucketInspector.java` for the reflective dump that
|
|
proves them at runtime.
|
|
|
|
### Quickstart
|
|
|
|
```bash
|
|
cd collections
|
|
export JAVA_HOME=/path/to/jdk-25 # must be JDK 25 or newer
|
|
export PATH="$JAVA_HOME/bin:$PATH"
|
|
|
|
mvn test # runs everything, including the HashMap-internals demos
|
|
```
|
|
|
|
The first `mvn` invocation in a fresh environment must run **online** (not `-o`) once, so the
|
|
compiler/surefire plugins populate the local repository cache; `-o` works for every run after
|
|
that. This is a Maven quirk, not specific to this module.
|
|
|
|
Run one scenario at a time with `./scripts/run.sh <name>` (`add-opens-fail`, `add-opens-ok`,
|
|
`treeify`, `cme`, `eqhash`, `bucket` — see the script for what each prints). Regenerate
|
|
every committed transcript in `docs/output/` with `./scripts/run-all.sh`.
|
|
|
|
**No HTTP endpoints in this module** — every demo is a plain Java `main()` method or a
|
|
JUnit test; there is nothing to start as a server. The diagnostic tool is
|
|
`BucketInspector.dump(HashMap)`, a reflective helper, not an endpoint.
|
|
|
|
### The one flag that matters: `--add-opens`
|
|
|
|
`BucketInspector` reflects into `java.util.HashMap`'s private `table` field to show the real
|
|
bucket layout. Since Java 9, that throws `InaccessibleObjectException` unless the JVM is
|
|
started with:
|
|
|
|
```
|
|
--add-opens java.base/java.util=ALL-UNNAMED
|
|
```
|
|
|
|
`pom.xml`'s surefire configuration sets this for `mvn test` automatically. Running
|
|
`ReflectionAddOpensDemo` directly with plain `java -cp ...` reproduces the crash on purpose
|
|
— see `docs/output/01-reflection-without-add-opens.txt`.
|
|
|
|
### Source layout
|
|
|
|
```
|
|
collections/
|
|
├── pom.xml
|
|
├── scripts/
|
|
│ ├── run.sh one named scenario at a time
|
|
│ └── run-all.sh regenerates every file in docs/output/
|
|
├── src/main/java/com/ankurm/interviewlab/collections/
|
|
│ ├── BucketInspector.java reflective bucket/bin dump (needs --add-opens)
|
|
│ ├── CollidingKey.java hashCode()==1 for every instance - forces one bucket
|
|
│ ├── ReflectionAddOpensDemo.java standalone entry point for the --add-opens demo
|
|
│ └── eqhash/
|
|
│ ├── EqualsOnlyPoint.java equals() overridden, hashCode() is NOT - the broken case
|
|
│ ├── CorrectPoint.java both overridden consistently - the fixed case
|
|
│ └── MutableKey.java a key whose hash-relevant field can change post-insertion
|
|
├── src/test/java/com/ankurm/interviewlab/collections/
|
|
│ ├── support/Transcript.java writes docs/output/NN-*.txt while the test asserts the same numbers
|
|
│ ├── BucketLayoutTest.java
|
|
│ ├── TreeifyTriggerTest.java
|
|
│ ├── ConcurrentModificationTest.java
|
|
│ └── EqualsHashCodeContractTest.java
|
|
└── docs/output/*.txt captured real output - see index below
|
|
```
|
|
|
|
### Index of captured output (`docs/output/`)
|
|
|
|
| File | What it proves |
|
|
|---|---|
|
|
| `01-reflection-without-add-opens.txt` | `InaccessibleObjectException`, uncaught, crashing the JVM — real stack trace |
|
|
| `02-reflection-with-add-opens.txt` | Same code, with the flag: a real bucket dump of a 2-entry `HashMap` |
|
|
| `03-bucket-layout-small-map.txt` | Real bucket indices for 6 string keys at default capacity 16 |
|
|
| `04-treeify-trigger.txt` | Capacity doubling 16→32→64 purely from `treeifyBin()`'s own resize calls, then a real `Node`→`TreeNode` conversion at insert #11 |
|
|
| `05-cme-classic-reproduction.txt` | A real `ConcurrentModificationException`, full stack trace, from `for`-each + `Collection.remove()` |
|
|
| `06-cme-silent-non-reproduction.txt` | The documented case where the SAME kind of mid-loop removal does **not** throw |
|
|
| `07-cme-concurrenthashmap-no-throw.txt` | `ConcurrentHashMap`'s weakly-consistent iterator mutated during iteration, never throws |
|
|
| `08-iterator-remove-safe.txt` | `Iterator.remove()` used correctly — no exception, correct result |
|
|
| `09-equals-hashcode-broken-contract.txt` | `equals()` without `hashCode()`: a real `HashSet` failing to deduplicate two equal objects |
|
|
| `10-mutable-key-lost-entry.txt` | A key mutated after insertion: the entry is unreachable by lookup but still physically present |
|
|
|
|
All ten are regenerated by one command (`./scripts/run-all.sh`); eight of the ten (03 through 10)
|
|
are generated by a JUnit test that also asserts the same numbers it prints, so a figure in the
|
|
article that stops being true fails the build, not just the next blog audit.
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|