Skip to main content
Dat 1. Sem Efterår 2025
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Sorteringsopgaver

FilmBase IX – Equals/HashCode og Sortering

Undgå dubletter i playlisten - igen!

Vi vil jo gerne undgå dubletter i playlisten og har tidligere sikret os ved at tjekken om vores ArrayListe allerede indeholder en bestemt film, inden vi tilføjer filmen til listen. Noget i stil med:

public class Playlist {
    private List<Film> films = new ArrayList<>();

    public boolean addFilm(Film film) {
        if (!films.contains(film)) {
            films.add(film);
            return true;
        } else {
            return false;
        }
    }

    public List<Film> listOfFilms() {
        return films;
    }
}

Vi har også skrevet testkode:

public void testPlaylistV1() {
    initFilms();
    Playlist playlist = new Playlist();

    playlist.addFilm(allFilms.get(0));
    playlist.addFilm(allFilms.get(0)); // dublet
    playlist.addFilm(allFilms.get(1));
    playlist.addFilm(allFilms.get(2));

    System.out.println("\nPlaylist:");
    printList(playlist.listOfFilms());
}

Og testen synes jo at virke (i dette tilfælde at The Godfather kun optræder en gang i output): testresultat

Vi får nu en erfaren tester til at kigge på vores test. Vi får at vide, at vi bør ændre på måden vi opretter vores filmobjekter i testkoden:

private void testPlaylistV2() {
    Playlist playlist = new Playlist();

    playlist.addFilm(new Film("The Godfather", 1972, Genre.Crime, Genre.Drama));
    playlist.addFilm(new Film("The Godfather", 1972, Genre.Crime, Genre.Drama)); // dublet
    playlist.addFilm(new Film("The Shawshank Redemption", 1994, Genre.Drama));
    playlist.addFilm(new Film("Schindler's List", 1993, Genre.Biography, Genre.Drama, Genre.History));

    System.out.println("\nPlaylist:");
    printList(playlist.listOfFilms());
}

Hvilket får vores test til ikke at opføre sig som forventet (de pokkers testere, de ødelægger da også det hele). Nu har vi The Godfather filmen i dublet.

testresultat

Hvad er forskellen på de to tests? Lad os prøve at debugge begge.

Konklusion på debugging: Så spørgsmålet er hvornår to objekter er ens ud fra et brugersynspunkt og ikke om de refererer til samme objekt. For strenge og tal er der en naturlig orden, f.eks. er ”apple” alfabetisk mindre end ”banana" fordi a kommer før b, men med egne klasser skal vi selv definere hvad der gør objekter ens. I Java bruger man hashCode og equals metoder til dette. Vi kunne jo beslutte at to film er ens hvis ”titel” og ”årstal” er ens. IntelliJ er god til at hjælpe med at genere hashCode og equals metoder. Stil dig i Film klasse og vælg “Generate equals() og hashCode()”:

intelliJ

intelliJ

equals() + hashCode()

I behøver ikke forstå koden i detaljer:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Film film = (Film) o;
    return year == film.year &&
           title.equalsIgnoreCase(film.title);
}

@Override
public int hashCode() {
    return Objects.hash(title.toLowerCase(), year);
}

Nu viser den nye test at vores kode virker, som den skal. Dubletten er forsvundet igen:

testresultat

Sortering

Når vi nu har defineret hvornår to film er ens (samme titel og årstal), vil det også være brugbart at kunne definere rækkefølge for sortering, så vi f.eks. kunne vise en liste med de nyeste film først.

ArrayList (eller rettere List interfacet) har en sort metode, så det ville være fedt at kunne skrive noget i retning af films.sort() og så give et sorteringskriterium med som argument. I Java har man Comparator interfacet til dette formål. Vi vil f.eks. kunne kalde films.sort(new YearComparator()); for at få sorteret en liste ud fra årstal.

Men inden vi kigger videre på denne løsning, så lad os se på hvordan sortering fungerer ved at kigge på sorteringsrobotten Youtube (se bare det første minut for at få en idé om hvordan sortering foregår).

robot

Robotten kan forstås som vores sort metode og rækken af kugler er vores liste. Hver kugle er et objekt:

Vi kan implementere et Comparator interface således:

public class YearComparator implements Comparator<Film> {
    @Override
    public int compare(Film f1, Film f2) {
        return Integer.compare(f1.getYear(), f2.getYear());
    }
}

Hvad er det compare metoden returnerer – en int? Undersøg hvad det betyder i Comparator dokumentationen Prøv det af, og se om du kan få et output som dette (med dine egne data, selvfølgelig):

test

Hvad hvis vi vil have de nyeste film først? Slå igen op i Comparator dokumentationen og se om der er en let måde at få vendt resultatet om.

Hvis man vil lave sortering på stedet uden at skulle oprette en Comparator klasse, kan man bruge et lambda udtryk i stedet:

films.sort((f1, f2) -> f1.getTitle().compareToIgnoreCase(f2.getTitle()));

Eller denne:

films.sort(Comparator.comparing(f -> f.getTitle().toLowerCase()));

Lav nu nogle metoder i Playlist klassen, der kan sortere

  • På titel
  • På årstal (nyeste film først)

Test at dine sorteringer virker.

Egentlig er det lidt irriterende at filmtitler der starter med fyldord som ”The ..” eller ”A/an…” påvirker hvor i playlisten titlen ender i forbindelse med en titel-sortering. Det ville give bedre overblik, hvis ”The Godfather” blev sorteret efter ”Godfather”. Det må du hellere fikse (String klassen har en substring som du måske kan bruge til at ignorere disse fyldord).

Test igen din sortering.

test

Konstatér at The Godfather kommer før Schindler’s List, fordi vi ser bort fra The i sorteringen.