ga('send', 'pageview');
Categories
Blogg

Vad är nytt i Java 21?

Java 21 släpptes till allmänheten den 19:e september och är full av spännande nyheter. Dessutom är denna release en Long Term Support (LTS) version, vilket betyder att det kommer uppdateringar och buggfixar ända fram till slutet av september 2026.

Då det endast gått drygt en vecka sen releasen så har mycket av Javas ekosystem inte hunnit anpassa sig till Java 21 ännu, så räkna med att olika IDE:er och byggsystem inte är redo ännu. Du kan dock använda alla features från Java 21 i Intellij IDEA om du i “Project Structure”-menyns “Language level”-dropdown väljer “21 (Preview)”. Om du istället använder Maven eller Gradle så har respektive byggsystem egna flaggor eller inställningar för att aktivera preview-funktionalitet i Java.

Bland de många nyheterna i Java 21 återfinns både syntaktiska ändringar och tillägg till standardbiblioteket. Vi kommer gå igenom några av de mest intressanta en efter en här nedan.

String Templates (preview)

En av de mest efterfrågade och efterlängtade syntaktiska tilläggen i Java är “string templates”, något som i andra språk ofta kallas “string interpolation”. Detta innebär att strängar automatiskt kan populeras med innehållet från variabler och dessutom formatera dessa variablers data korrekt. I exemplet nedan visas hur man kan kombinera flerrads-strängar med string templates för sträng- och heltalsvariabler:

private static void stringTemplates() {
    var name = "Ola";
    var age = 100;
    var str = STR."""
           Hejsan! Your name is \{name} and your are \{age} years old.""";
    System.out.println(str);
}

Notera prefixet STR. framför strängen, vilket markerar den som en string template. Variablerna som ska interpoleras i strängen refereras med variabelnamnen och omgärdas av curly brackets prefixade med ett backslash.

Det bör dock nämnas att denna feature fortfarande är i preview-fas, vilket innebär att framtida Java-versioner kan komma att ändra syntax eller beteende, så denna string templates bör inte användas i produktionskod riktigt ännu.

Referenser: JEP-430

Sequenced collections

Detta tillägg till standardbiblioteket är ganska blygsamt men det besparar oss utvecklare ändå en del boilerplate-kod. Hur många gånger har du exempelvis skrivit list.get(list.size()-1) för att få det sista elementet i exempelvis en ArrayList? Med sequenced collections behöver du bara skriva list.getLast() för att åstadkomma samma sak. Se exemplet nedan för ytterligare kontext:

private static void sequencedCollections() {
    var myList = new ArrayList<>(List.of(1,2,3,4,5));
    System.out.printf("The list %s (reversed: %s) has the last element %s and first element %s.%n",
           myList, myList.reversed(), myList.getLast(), myList.getFirst());
    myList.addFirst(0);
    System.out.printf("List contents: %s%n", myList); // 0,1,2,3,4,5
    myList.addLast(6);
    System.out.printf("List contents: %s%n", myList); // 0,1,2,3,4,5,6
}

Utöver metoderna som visas ovan finns även metoderna removeFirst och removeLast. Flera existerande Collections-klasser i standardbiblioteket har redan uppdaterats med detta nya interface.

Referenser: JEP-431

Record patterns och Pattern matching for switch

Redan i Java 16 så fick vi en ny feature kallad “Pattern matching for instanceof”, vilken lät oss ta följande boilerplate-kod…

if (obj instanceof String) {
   String s = (String) obj;
   // ...
}

…och skriva om den till det något mer koncisa:

if (obj instanceof String s) {
   // ...
}

Detta togs emot med öppna armar av Java-världen, särskilt bland dem som arbetat med kodbaser där typecasting är vanligt förekommande. Efter det fortsatte språkets designers lägga till pattern matching till Java. Ett av dessa tillägg är att man kan använda instanceof implicit i switch-satser. Dessutom kan man nu även matcha inte bara Record-klasser i switch-satser utan även använda sig av en sorts “destructuring” för att plocka ut enskilda fält i Records. Detta kan även göras i flera nivåer, det vill säga att man kan referera till fält inuti Records inuti Records (i folkmun kallat Recordception). En preview-feature i Java 21 låter oss även slippa definiera alla fält vi vill matcha med hjälp av så kallade “unnamed variables”. Ett exempel på detta syns här nedan:

record Coordinate(int x, int y, int z) {}
private static void recordPatterns(Object obj) {
    switch (obj) {
        case Coordinate(int x, int y, _) -> System.out.printf("The x and y coordinates are (%d,%d)%n", x, y);
        case null, default -> System.out.println("Error: null or unknown object");
    }
}

Det är svårt att få plats med alla olika fall av pattern matching för Records i switch-satser i ett kort exempel, så jag rekommenderar att du kollar in exemplen i JEP-440 och JEP-441 för att få en bättre förståelse för denna kraftfulla feature.

Referenser: JEP-440, JEP-441, JEP-394, JEP-443

Structured Concurrency (preview)

Denna mycket hjälpsamma feature kan beskrivas som en try-catch-sats för trådar. Structured concurrency är en blandning av syntaktiska ändringar och bibliotekstillägg ämnade att förenkla felhantering och städning efter trådar. Språkets designers identifierade några vanliga användningsfall för flertrådad kod och skapade nya klasser för att hantera dessa på ett både enkelt och säkert sätt, men utan att kompromissa med prestandan. Man har tagit särskild hänsyn till vanliga misstag som utvecklare gör när de arbetar med flertrådad kod i Java och har utvecklat denna feature för att minimera risken för att göra dessa fel. I exemplet nedan visas hur man kan hantera två parallellt arbetande trådar där båda trådarna ska avslutas och resurserna frigöras ifall minst av dem slänger ett exception:

private static void structuredConcurrency() throws InterruptedException, ExecutionException {
    // This structured task scope cancels execution and cleans up if any exceptions are thrown.
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String> supplierA  = scope.fork(() -> {
            Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000, 2000));
            return "Result from supplier A";
        });
        Supplier<String> supplierB = scope.fork(() -> {
            Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000, 2000));
            return "Result from supplier B";
        });
        scope.join().throwIfFailed();
        System.out.printf("Result A=%s and B=%s%n", supplierA.get(), supplierB.get());
    }
    // …
}

I exemplet ovan kommer koden att skapa två Task-instanser och sedan vänta på att båda blir färdiga. Därefter kommer resultaten av deras arbete, två strängar, att inhämtas och skrivas ut i terminalen.

I andra fall kan det vara mer användbart att starta flera parallella Tasks och returnera ett resultat så fort en av dem är klara, varefter alla andra ska stoppas och städas bort. Även för detta fall finns nu en nyskapad klass i standardbiblioteket: 

private static void structuredConcurrency() throws InterruptedException, ExecutionException {
    // …
    // This structured task scope cancels execution and cleans up as soon as the first task finishes.
    List<Callable<String>> tasks = List.of(
        () -> {
            Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000, 2000));
            return "Result from supplier A";
        },
        () -> {
            Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000, 2000));
            return "Result from supplier B";
        }
    );
    var deadline = Instant.now().plusMillis(1500);
    try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
        for (var task : tasks) {
            scope.fork(task);
        }
        String result = scope.joinUntil(deadline)
               .result();
        System.out.println("Winner of the race was: " + result);
    } catch (TimeoutException e) { // if no tasks finish within the deadline, a timeout exception is raised.
        System.out.println("None of the tasks succeeded within the deadline");
    }
}

I detta exempel skapas först två Task-instanser, varefter dom fork:as och scope-instansen blockerar tills en av dem är färdig eller deadlinen på 1,5 sekunder uppnåtts. Vinnaren av kapplöpningen får sedan sitt resultat utskrivet i terminalen, eller så hamnar vi i catch-satsen för TimeoutException och meddelar där att deadlinen överskridits. I båda fallen kommer dock trådarna att avslutas och deras resurser frigöras på rätt sätt.

Notera att även denna feature fortfarande är i preview-stadiet och kan komma att ändras i framtida Java-versioner.

Referenser: JEP-453 

Virtual Threads

Sist men inte minst har vi det kanske största och mest revolutionerande tillägget till Java i form av virtuella trådar (virtual threads). Detta är en feature som i andra programmeringsspråk ofta kallas för “green threads”, “fibers” eller “coroutines”. Det rör sig om en typ av lättviktiga trådar som hanteras av JVM:en istället för av operativsystemet men som ändå delar på en OS-tråd. Precis som i andra språk så passar denna typ av trådar perfekt för I/O-operationer som involverar en del väntetid, exempelvis stora antal läsningar och skrivningar till lokala hårddiskar eller kommunikation över nätverk, båda områden som Java haft svårt att mäta sig med andra språk på utan hjälp av tredjepartsbibliotek.

Här nedan syns ett enkelt exempel på hur man startar 10000 virtuella trådar som alla väntar 1 sekund innan de returnerar ett heltal:

private static void virtualThreads() {
    System.out.println("Creating and running 10k virtual threads that sleep 1 sec");
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        IntStream.range(0, 10_000).forEach(i -> {
            executor.submit(() -> {
                Thread.sleep(Duration.ofSeconds(1));
                return i;
            });
        });
    }
    System.out.println("Done creating and running virtual threads");
}

Det är värt att notera hur snabbt denna kod exekveras och hur långsamt det går om du ersätter den tredje radens metodanrop med Executors.newThreadPerTaskExecutor() vilket istället skulle skapa tiotusen OS-trådar.

Då virtuella trådar är en färdig feature kan du börja använda den i din egen kod redan nu, men håll i åtanke att existerande bibliotek, ramverk och applikationer inte hunnit anamma denna feature ännu. När populära webbservrar, web service-ramverk och databaser börjar använda Javas virtuella trådar kommer vi troligen få se både förbättringar av prestanda och lägre minnesanvändning.

Referenser: JEP-444


Detta var bara ett urval av de 15 nya features som inkluderats i Java 21. För att se vad mer som ingår rekommenderar jag att läsa listan av JEP:er för denna release på OpenJDKs hemsida. All kod i artikeln finns dessutom som ett fullständigt och körbart exempel i min publika GitHub Gist här.

By Ola Rende

Backendutvecklare, fullstackutvecklare

Leave a Reply

Your email address will not be published. Required fields are marked *