Java version 19 är här
6 minuter i lästid Java

Java version 19 är här

Java JDK version 19 släpptes i förra veckan. Nu mera släpper Oracle en ny version två gånger per år (vår och höst). Version 19 innehåller ett antal förhandsversioner av funktionalitet (preview features), som vi ska kika på i detta inlägg.

Ny funktionalitet i Java brukar oftast utvecklas i projektform, vilket innebär att det inom ett dylikt projekt kan föras en diskussion om nya egenskaper och olika implementationsförslag kan utvärderas. Med tiden kristalliseras vad man vill gå vidare med för inkorporering i språket Java respektive dess bibliotek.

De projekt som är aktuella för Java 19 är

  • Project Amber: pattern matching, switch expressions, records
  • Project Loom: virtual threads, co-routines
  • Project Panama: invocation of native code, replacement of JNI

Vad innebär preview?

En förhandsversion av en funktionalitet i Java innebär att vissa saker kan förändras. Såsom "Pattern matching for switch" är inne på den tredje förhandsversionen.

För att kunna kompilera och exekvera ett Java program som använder sig av förhandsfunktioner måste man bifoga en flagga

javac -d cls --enable-preview --release 19 MyApp.java
java -cp cls --enable-preview MyApp

Om det bara är en källkodsfil kan du kompilera och köra direkt

java --enable-preview --source 19 src/Amber.java

Förbättrad switch

Sedan tidigare så har den gamla switch-satsen evolverat till att också formuleras som ett uttryck (expression). Den funktionalitet som förhandsvisas nu är matchning på type eventuellt tillsammans med ett för-villkor.

Object obj = ...;
var result = switch(obj) {
  case Integer val when val < 0  -> "Negative";
  case Integer val when val == 0 -> "Zero";
  case Integer val               -> "Positive";
  default                        -> "Unknown";
};

Här följer ett komplett exempel. Tanken är en funktion i ett hypotetiskt system för syntax-färgning (syntax coloring), som tar ett objekt och returnerar en HTML span tag med CSS class och formaterat värde. Först har vi den klassiska versionen med en switch-sats.

String renderClassic(Object obj) {
    String css = "unknown";
    String txt = obj != null ? obj.toString() : "null";
    if (obj instanceof Integer || obj instanceof Long) {
        css = "integral";
        txt = Long.toString(((Number) obj).longValue());
    } else if (obj instanceof Float || obj instanceof Double) {
        css = "real";
        txt = format("%.5f", ((Number) obj).doubleValue());
    } else if (obj instanceof Boolean) {
        css = "bool";
        txt = (Boolean) obj ? "True" : "False";
    } else if (obj instanceof Date) {
        css = "date";
        txt = format("%tF", (Date) obj);
    } else if (obj instanceof String) {
        css = "string";
        txt = obj.toString();
    }
    return format("<span class=\"%s\">%s</span>", css, txt);
}

Matar vi denna med följande värden

Object[] values = { 2 * 21, -17, 4 * atan(1),
  (42 == 40 + 2), ("Hi" + " " + "Java"), "    ",
  new Date(), Runtime.version(), null };

void test_renderClassic() {
    for (Object obj : values)
        System.out.printf("(C) %s%n", renderClassic(obj));
}

Blir utskriften på följande sätt

(C) <span class="integral">42</span>
(C) <span class="integral">-17</span>
(C) <span class="real">3,14159</span>
(C) <span class="bool">True</span>
(C) <span class="string">Hi Java</span>
(C) <span class="string">    </span>
(C) <span class="date">2022-09-29</span>
(C) <span class="unknown">19+36-2238</span>
(C) <span class="unknown">null</span>

Den moderna version börjar med att klassificera vad det är för indata och använder resultatet för att skapa en HTML span tag.

String renderModern(Object obj) {
    var c = classify(obj);
    return format("<span class=\"%s\">%s</span>", c.css, c.txt);
}

Funktionen classify returnerar en record Pair. Records kom i Java 16, men har figurerat som förhandsversioner innan dess.

record Pair(String css, String txt) { }

Så här ser funktionen classify ut.

Pair classify(Object obj) {
    return switch (obj) {
        case null -> 
                new Pair("unknown", "null");
        case Integer val when val < 0 -> 
                new Pair("integral negative", Integer.toString(val));
        case Integer val -> 
                new Pair("integral", Integer.toString(val));
        case Long val when val < 0 -> 
                new Pair("integral negative", Long.toString(val));
        case Long val -> 
                new Pair("integral", Long.toString(val));
        case Float val -> 
                new Pair("real", format("%.5f", val));
        case Double val -> 
                new Pair("real", format("%.5f", val));
        case Boolean val when true -> 
                new Pair("bool", val ? "True" : "False");
        case Date val -> 
                new Pair("date", format("%tF", val));
        case String val when val.isBlank() -> 
                new Pair("string blank", "_");
        case String val -> 
                new Pair("string", val);
        default -> 
                new Pair("unknown", obj.toString());
    };
}

Anropar vi funktionen med samma data som tidigare får vi denna utskrift.

(M) <span class="integral">42</span>
(M) <span class="integral negative">-17</span>
(M) <span class="real">3,14159</span>
(M) <span class="bool">True</span>
(M) <span class="string">Hi Java</span>
(M) <span class="string blank">_</span>
(M) <span class="date">2022-09-29</span>
(M) <span class="unknown">19+36-2238</span>
(M) <span class="unknown">null</span>

Destrukturering av sammansatta data

Om du programmerat i modern JavaScript (dvs EcmaScript), så kanske du känner till att man kan packa upp ett sammansatt objekt.

//JavaScript
const obj = {name:'Nisse', addr:{street:'Hacker Lane', city:'Perl Lake'}}
const {name, addr:{street}} = obj
console.log('%s bor på %s', name, street)
--> Nisse bor på Hacker Lane

Liknande egenskaper finns i andra språk. Till exempel i C++ kan man packa upp en enkel struct eller std::tuple.

//C++
#include <iostream>
#include <string>
struct Data {
    std::string name;
    unsigned age;
};
int main() {
    auto p = Data{"Bosse", 42};
    auto [n, a] = p;
    std::cout << n << ":" << a << "\n";
}
--> Bosse:42

En variant på detta finns nu som en förhandsvisning, där man kan dekonstruera en record. Säg att vi har följande två records.

record Address (String street, String city, int code) {}
record Person (String name, Address addr, int age) {}

Vi skapar ett person-objekt och skickar detta till funktionen doit.

void run() {
    var p = new Person("Nisse Hult", 
                       new Address("Hacker Lane", "Javaville", 8080), 
                       42);
    doit(p);
}

I denna funktion packar vi upp det sammansatta objekt och plockar ut det vi behöver.

void doit(Object obj) {
    if (obj instanceof Person(String n, Address(String s, String c, int o), int a))
        System.out.printf("%s lives on %s", n, s);
}

Kör vi programmet vår vi veta på vilken gata Nisse bor.

$ java  --enable-preview --source 19 src/Amber2.java
Note: src\Amber2.java uses preview features of Java SE 19.
Note: Recompile with -Xlint:preview for details.
Nisse Hult lives on Hacker Lane

I funktionen doit, hade vi med ett antal variabler i matchningen som inte behövdes. Snart (hoppas vi) kommer man kunna ersätta dessa med en så kallad don't care variabel i form av ett understrykningstecken (_).

if (obj instanceof Person(String n, Address(String s, String _, int _), int _))
    System.out.printf("%s lives on %s", n, s);

Bruket med understrykningstecken för att markera ointressanta delar kommer från språk som Prolog och Erlang (influerad av Prolog).

I nästa avsnitt ska vi kika på virtuella trådar (virtual threads) från project Loom.

Länkar