Dataslope logoDataslope

Capstone: A Music Library

Bring lists, sets, maps, comparators, and immutability together in one multi-file challenge

We've covered the whole map: the four collection families, generic types, equality and hashing, comparators, iteration, immutability, and architecture. This chapter is one substantial challenge that exercises all of it.

You'll build a small MusicLibrary that ingests tracks and answers four queries:

  1. byArtist(name) — all tracks for an artist, in insertion order
  2. genres() — the set of all unique genres
  3. topRated(n) — the top n tracks by rating (ties broken by title)
  4. albumsByArtist(name) — the unique album names for an artist, in insertion order

Each query corresponds directly to a collection shape we've studied:

QueryShape
byArtist(name)Map<String, List<Track>>
genres()Set<String> (insertion-ordered if we want a deterministic preview)
topRated(n)List<Track> sorted with a Comparator
albumsByArtist(name)Map<String, Set<String>> (insertion-ordered set)

What "good" looks like

A clean implementation will:

  • Store Tracks in the shape that matches each query (not a flat list scanned every time).
  • Use computeIfAbsent to build per-artist lists and sets lazily.
  • Use a LinkedHashSet where insertion-ordered uniqueness matters.
  • Build topRated with a Comparator.comparingInt(...).reversed().thenComparing(...) chain.
  • Return read-only / snapshot collections from every getter.

The challenge

Challenge
Java 8 (Update 492)
Build the MusicLibrary

Implement MusicLibrary so that the driver in Main prints exactly:

Debussy tracks:
Clair de Lune
Reverie
La Mer
Genres: [classical, impressionist, orchestral]
Top 3:
Clair de Lune (5)
La Mer (5)
Liebestraum (4)
Debussy albums: [Suite Bergamasque, Reveries, Sea Pieces]

Hints:

  • computeIfAbsent for "map of list" and "map of set" insertion.
  • For Genres: use a LinkedHashSet<String> so iteration order is deterministic (insertion order).
  • For topRated, sort a copy of the tracks list with Comparator.comparingInt(Track::rating).reversed().thenComparing(Track::title) and take the first n.
  • For albumsByArtist, use LinkedHashSet<String> per artist.
  • Return safe views from every public method (Collections.unmodifiableList / Collections.unmodifiableSet).

What you just demonstrated

That single class touches every major idea in the course:

  • Right shape per query — one map of lists, one map of sets, one set, one list (Data Modeling).
  • computeIfAbsent for "map of \*" — concise, race-free against itself (Maps).
  • LinkedHashSet — uniqueness and insertion order (Sets).
  • Comparator compositionreversed().thenComparing(...) to combine two ordering criteria (Comparable & Comparator).
  • Safe getters — every returned collection is an unmodifiable view (Immutable Collections + Architecture).
  • record-backed value objects for Track — typed data, free equals/hashCode/toString (Equality and Hashing).

Test your understanding

QuestionSelect one

Why LinkedHashSet<String> for genres() rather than HashSet<String>?

It's faster

It allows duplicates

It keeps insertion order, which makes the printed output deterministic and matches the user-visible "as encountered" order

It supports concurrent reads

QuestionSelect one

In topRated, why sort a copy of the tracks list?

It is faster

Because sorting in place would permanently reorder the library, making "insertion order" queries (like byArtist) wrong on subsequent calls

The Comparator requires a fresh list

The original list is unmodifiable

On this page