Gammel hund

Java er et konservativt språk som verdsetter bakoverkompatibilitet over fancy syntaks. Vi programmerere elsker å velte oss i skinnende nye features, men om du har drevet med utvikling en stund, så vet du også å sette pris på stabilitet. Java 21 ble akkurat sluppet, og da er det verdt å ta en kikk på hvordan språket har utviklet seg i det siste.

Records i Java

Hvis du har programmert Java før, så har du helt sikkert fått med deg at records er blitt en del av språket allerede. Andre språk har hatt lignende funksjonalitet lenge, så det var på høy tid da det landet i Java 16 i 2021.

Med records så slipper du å skrive boilerplate som constructor, gettere, equals, hashcode og toString selv. I tillegg så er records immutable, det vil si du kan ikke endre på verdiene når de først er satt.

Immutable?

Hva betyr det i praksis? La oss opprette en record, og prøve å endre på et felt.

record Hund(String navn, int alder, List<String> venner) {}

var fido = new Hund("Fido", 5, new ArrayList<>(List.of("Hexi", "Loke")));
fido.navn = "Knut"; // Kompilerer ikke fordi navn er final

Som forventet så virker ikke det. Det følgende derimot, er lov;

fido.venner.add("Storm"); // Muterer innholdet i listen
System.out.println(fido);   // Hund[navn=Fido, alder=5 venner=[Hexi, Loke, Storm]]

Records er altså ikke immutable hvis et felt er mutable.

Validering av records

For å unngå ulovlig tilstand i en applikasjon, så vil man gjerne validere records ved opprettelse. En ting jeg savner er muligheten til å sette default-verdier, så enn så lenge må du håndtere det selv. Det kan man fint gjøre med den kompakte constructor-varianten:

record Hund(String navn, int alder, List<String> venner) {
    public Hund {
      if (navn == null || navn.isBlank()) throw new IllegalArgumentException("Navn kan ikke være tomt");
      if (alder < 0) throw new IllegalArgumentException("Alder må være positiv");
      if (venner == null) venner = List.of();
    }
  }

For mer avanserte datastrukturer så kan man fint bruke Builder-patternet for å støtte validering og default-verdier, med den ekstra koden det medfører selvsagt.

Oppsummert så er records et nyttig verktøy å ha i verktøykassen, og er definitivt et steg i riktig retning for Java.

Gammel hund

Hva er nytt i Java 21 for records?

En record er lett å konstruere, og nå kan man endelig hente ut data via record patterns. Dette er en populær feature i andre språk, som f.eks JavaScript og Clojure.

Hvordan funker det?

Før record-patterns så måtte man plukke ut verdier selv:

static void printHund(Object obj) {
    if (obj instanceof Hund h) {
        System.out.println(h.navn() + " er " + h.alder() " år gammel");
    }
}

I Java 21 kan du nå hente ut verdiene fra recorden på følgende måte:

static void printHund(Hund obj) {
    if (obj instanceof Hund(String navn, int alder, var venner)) {
        System.out.println(navn + " er " + alder + " år gammel");
    }
}

Du kan spesifisere typen til feltene i recorden, eller du kan bruke var.

Du kan gjøre destructuring i flere nivåer også.

record Katt(String navn, Hund fiende) {}

var katt = new Katt("Mjau", fido);

if (roach instanceof Katt(var navn, Hund(var hundeNavn, var _x, var _y))) {
      System.out.println("Katten heter " + navn + " og eies av " + hundeNavn );
}

Merk at du spesifisere alle feltene i en record når du destructurer, selv om du ikke trenger de.

Dette blir riktignok litt bedre når JEP-443 ferdigstilles, men den featuren er kun i preview i Java 21. Da kan du markere parameter som unødvendige ved å bruke underscore.

Jeg lurer litt på hvorfor Java har valgt å basere record patterns på posisjon og ikke navn, slik som f.eks JavaScript Problemet er illustrert i eksemplet under. Her er det fort gjort å blande navnene, ettersom navnet på record-feltene er posisjonsbaserte:

if (obj instanceof Hund(String venner, int navn, var alder)) {
        System.out.println(venner + " er " + navn + " år gammel");
}

Pattern matching med switch

Gammel hund

Switch i Java har frem til nå vært sørgelige greier. Med Java 21, så kan man endelig gjøre pattern matching.

Da kan man f.eks gjøre følgende, hvor man matcher mot typen til objektet:

static String formattere(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}

Nå støtter switch matching mot null:

static String behandle(String input) {
    return switch (input) {
      case null -> "Er null";
      case "Fido", "Hexi" -> "En hund";
      default -> "Noe annet";
    };
}

Hvis du har en null-sjekk i switchen så slipper du å sjekke det på forhånd. Merk at default ikke matcher mot null, så du kan fortsatt få NullPointer i en switch om du ikke har med null-casen.

Du kan nå også begrense en case med en påfølgende when:

static String aldersVurdering(Hund hund) {
    return switch (hund) {
      case Hund(var _n, var alder, var _v) when (alder < 3) -> "Ung";
      case Hund(var _n, var alder, var _v) when (alder < 10 ) -> "Gammel";
      default -> "Kjempegammel";
    };
  }

Det blir mer lesbart ettersom du da får et skille mellom matchingen på venstresiden av pilen og hvordan tilfellet skal håndteres på høyresiden.

Virtual threads

Dette er den mest spennende featuren i Java 21, men den fortjener en egen bloggpost. Sammen med structured concurrency (som er i preview), så åpner man opp for en forenklet programmeringsmodell for concurrency.

Er Java et spennende alternativ i 2023?

Gammel hund

Java har “the last mover advantage”. Det vil si at man legger til ny funksjonalitet etter at de mer fremoverlente språkene har avslørt hva som fungerer bra i praksis. Ved å levere endringer i små steg, så kan Java oppdateres mye oftere enn tidligere. Det snakket en av arkitektene bak Java, Brian Goetz, om på årets Devoxx.

Java blir altså gradvis mer moderne. Det blir spennende å følge med i årene fremover om denne nye strategien vil holde Java relevant i konkurransen med andre språk.