Inselupdate: enum im switch mit Pattern-Matching in Java 21

Seit Java 21 ist die Möglichkeit vom switch stark erweitert worden. Das betrifft auch Aufzählungstypen. Es war immer ein wenig umständlich, wenn ein switch Konstanten von verschiedenen Aufzählungstypen vergleichen sollte. Das ist einfacher geworden und dazu ein kleines Beispiel. Nehmen wir zwei enum an, die keine Typbeziehung haben:

enum CoffeeSize { SMALL, LARGE }

enum TeeSize { SMALL, LARGE }

Die Größen sollen nun zusammen behandelt werden. Wir können schreiben:

static void orderSize( Enum size ) {

  switch ( size ) {

    case CoffeeSize.SMALL -> System.out.println( "Small" );

    case TeeSize.SMALL -> System.out.println( "Small" );

    case CoffeeSize.LARGE -> System.out.println( "Large" );

    case TeeSize.LARGE -> System.out.println( "Large" );

    default -> {

    }

  }

}

Da der Compiler von allen einen beliebigen Enum ausgeht, kann er keine vollständige Abdeckung erkennen. Wir können das mit einem Basistyp allerdings lösen,

Wenn ein switch-Ausdruck mit einem einzelnen Aufzählungstyp verwendet wird, kann der Compiler überprüfen, ob alle Aufzählungselemente abgedeckt sind und ob ein default-Zweig entfallen kann. Der Compiler kann auch eine vollständige Abdeckung überprüfen, wenn der switch-Block verschiedene Aufzählungstypen behandelt und dabei ein versiegeltes Interface als Basistyp verwendet wird. Betragen wir das Beispiel aus dem vorherigen Kapitel erneut und schreiben es um:

sealed interface DrinkSize permits CoffeeSize, TeeSize {}

enum CoffeeSize implements DrinkSize { SMALL, LARGE }

enum TeeSize implements DrinkSize { SMALL, LARGE }




static void orderSize( DrinkSize size ) {

  switch ( size ) {

    case CoffeeSize.SMALL -> System.out.println( "Small" );

    case TeeSize.SMALL -> System.out.println( "Small" );

    case CoffeeSize.LARGE -> System.out.println( "Large" );

    case TeeSize.LARGE -> System.out.println( "Large" );

  }

}

 

Inselupdate: Neue Math Methoden clamp(…)

Seit Java 21 gibt es in der Klasse Math neue Methoden, die einen Wert in einem Bereich halten:

  • static double clamp(double value, double min, double man)
  • static float clamp(float value, float min, float max)
  • static int clamp(long value, int min, int man)
  • static long clamp(long value, long min, long max)

Die Methoden basieren im Kern auf einem verschachtelten Math.min(max, Math.max(value, min)), lösen aber Ausnahmen aus, wenn der Endwert vor dem Startwert liegt.

Inselupdate: Records implementieren Schnittstellen

Obwohl Records keine Klassen erweitern können, können sie Schnittstellen implementieren. Dies ermöglicht uns, Abstraktionen zu erstellen, was nützlich ist, um gemeinsame Record-Komponenten zu teilen.

Betrachten wir ein Beispiel. Wir wollen den Basistyp Event nicht mehr als abstrakte Oberklasse deklarieren, sondern als Schnittstelle:

interface Event {

  String about();

  int duration();

}

Das erlaubt es uns, zwei Records zu deklarieren, die die Event-Schnittstelle implementieren:

record Nap( String about, int duration ) implements Event {}

record Workout( String about, int duration, int caloriesBurned ) implements Event {}

Der clevere Teil dabei ist, dass die Records bereits die Zugriffsmethoden String about() und int duration() besitzen, sodass keine zusätzliche Implementierung erforderlich ist.

Mit dieser Typbeziehung können wir Folgendes tun:

Event event = new Nap( "Snooze Olympics", 69 );

System.out.println( event.about() );

System.out.println( event.duration() );

In diesem Fall ist der Referenztyp Event und der Objekttyp Nap. Mit dieser Abstraktion lässt sich perfekt Pattern-Matching und Record-Pattern einsetzen:

switch ( event ) {

  case Nap nap ->

      System.out.printf( "%d minutes of ninja-level rest!%n", nap.duration );

  case Workout( var about, var duration, var calories ) ->

      System.out.printf("You just burned %d calories for a guilt-free gummy bear.%n", calories );

  default -> {}

}

Inselupdate: Record-Patterns in Java 21

Führen wir für die folgenden Beispiele ein Record für Punkte ein:

record Point( int x, int y ) {}

Nun möchten wir überprüfen, ob die X-Y-Koordinaten eines Punktes auf null stehen. Dafür erstellen wir eine Methode namens isZeroPoint(…), die alle Objekttypen akzeptiert und false zurückgibt, wenn es sich nicht um einen Punkt handelt:

static boolean isZeroPoint( Point object ) {

  if ( object instanceof Point ) {

    Point point = (Point) object;

    return point.x() == 0 && point.y() == 0;

  }

  return false;

}

Eine kompaktere Version des Tests, die auf eine Zwischenvariable verzichtet, kann so aussehen:

return object instanceof Point && ((Point) object).x() == 0 && ((Point) object).y() == 0;

Lesbarerer ist es nicht.

Der Einsatz einer Pattern-Variable verbessert die Lesbarkeit und Klarheit des Codes:

static boolean isZeroPoint( Object object ) {

  return object instanceof Point p && p.x() == 0 && p.y() == 0;

}

Der Test p.x() == 0 && p.y() == 0 lässt sich mit einem Trick noch weiter verkürzen:
static boolean isZeroPoint( Object object ) {

  return object instanceof Point p && (p.x() | p.y()) == 0;

}

Wenn zwei Zahlen mittels der bitweisen ODER-Operation verknüpft werden und eine davon nicht null ist, wird auch das Ergebnis nicht null sein. Dies ermöglicht eine elegante und kompakte Überprüfung der Nullbedingung für beide Koordinaten gleichzeitig.

Record-Pattern einsetzen

Auffällig an der bisherigen Lösung ist die Notwendigkeit einer Point-Variable p, um auf p.x() und p.y() zuzugreifen. Java 21 führt Record-Pattern[1] ein, die in anderen Programmiersprachen als Destrukturierung bezeichnet werden. Record-Pattern können sowohl bei instanceof als auch bei switch verwendet werden. Hier ist ein Beispiel für instanceof, wodurch isZeroPoint(…) etwas kürzer wird:

static boolean isZeroPoint( Point point ) {

  return point instanceof Point( int a, int b ) && (a | b) == 0;

}

Der Teil Point(int a, int b) nennt sich Record-Pattern. Nach einer Übereinstimmung werden neue lokale Variablen a und b eingeführt, die vom Punkt die Koordinaten enthalten. Das heißt, a wird mit point.x() und b mit point.y() belegt. Die Variablennamen müssen nicht mit den Record-Komponentennamen übereinstimmen; in diesem Fall heißen sie a und b, x und y wären aber möglich. Wichtig ist, alle Record-Komponenten aufzulisten; keine Record-Komponente darf ausgelassen werden.

Kommen wir von Punkten zu Linien. Betrachten wir ein neues Record:

record Line( Point start, Point end ) {}

Schreiben wir eine zweite Methode isZeroLine(…), die überprüft, ob die beiden Punkte der Linie Null sind oder nicht. Beginnen wir mit einer Pattern-Variablen:

static boolean isZeroLine( Object object ) {

  return    object instanceof Line line

         && (  line.start().x() | line.start().y()

             | line.end().x()   | line.end().y() ) == 0;

}

Da es sich bei Line um einen Record handelt, kann das Record-Pattern angewendet werden:

static boolean isZeroLine( Object object ) {

  return    object instanceof Line( Point start, Point end )

         && (start.x() | start.y() | end.x() | end.y()) == 0;

}

Die Datentypen können mit var abgekürzt werden:

static boolean isZeroLine( Object object ) {

  return    object instanceof Line( var start, var end )

         && (start.x() | start.y() | end.x() | end.y()) == 0;

}

Geschachtelte Record-Pattern

Record-Pattern können auch verschachtelt werden, um komplexere Strukturen abzubilden:

static boolean isZeroLine( Object object ) {

  return object instanceof Line(

    Point( int x1, int y1 ), Point( int x2, int y2 )

   ) && (x1 | y1 | x2 | y2) == 0;

}

Das Code-Volumen schrumpft nicht, daher ist es Geschmacksache, ob die Variante besser ist.

Record-Pattern bei switch

Das Pattern-Matching bei switch ist in Java 21 noch leistungsfähiger geworden. Erstellen wir eine zweite Methode isZero(Object), die sowohl Punkte als auch Linien überprüfen kann. Lösen wir die Aufgabe zuerst wie bekannt mit Pattern-Variablen:

static boolean isZero( Object o ) {

  return switch ( o ) {

    case Point p -> (p.x() | p.y()) == 0;

    case Line l -> (l.start().x() | l.start().y() | l.end().x() | l.end().y()) == 0;

    default -> false;

  };

}

Neben instanceof sind Record-Pattern auch bei switch-case möglich. Die Methode kann wie folgt umgeschrieben werden:

static boolean isZero( Object o ) {

  return switch ( o ) {

    case Point( int x, int y ) -> (x | y) == 0;

    case Line( Point s, Point e ) -> isZero( s ) && isZero( e );

    default -> false;

  };

}

Bei der Linie lässt sich das Record-Pattern wieder schachteln:

static boolean isZero( Object o ) {

  return switch ( o ) {

    …

    case Line( Point( int x1, int y1 ), Point( int x2, int y2 ) )

          -> (x1 | y1 | x2 | y2) == 0;

    …

  };

}

Allerdings ist auch hier der Code wieder länger.

Pattern-Matching mit Record-Pattern und when

Wir haben gesehen, dass Pattern-Matching mit Record-Pattern zur Destrukturierung möglich ist. Auch lässt sich ein when für eine weitere Abfrage einsetzen. Die Bedingung hinter when kann auf die Pattern-Variable oder Variable aus dem Record-Pattern zugreifen.

Ein Beispiel: Wir möchten Candy und Book als Records implementieren:

record Candy( int calories ) {}

record Book( String title, int numberOfPages ) {}

Ein Block kann wie folgt Ausgaben zu unterschiedlichen Objekttypen formulieren:

Object object = new Candy( 120 );




switch ( object ) {

  case Candy candy

  when candy.calories > 10_000 ->

      System.out.println( "Are you trying to sweeten the whole world?" );




  case Candy candy ->

      System.out.println("Is this candy trying to start a dance party in my mouth?");




  case Book( var title, var pages )

  when pages > 100 ->

      System.out.println(

          "Looks like someone was on a mission to make the dictionary jealous." );




  case Book( var title, var pages )

  when title.isEmpty() ->

      System.out.println("Diving into books that forgot to introduce themselves.");




  case Book -> System.out.println( "Opening minds, one page at a time" );




  default -> System.out.println("Who knew boredom could be so three-dimensional?");

};

Das Beispiel zeigt Pattern-Matching sowohl ohne als auch mit Record-Pattern. Auch hier müssen wir erneut die Dominanz beachten. Es wäre inkorrekt, case Book -> über die anderen Fallblöcke case Book( var title, var pages ) when … zu setzen.

[1] https://openjdk.org/jeps/440

Inselupdate: Pattern-Matching bei switch in Java 21

Wir haben bereits gesehen, dass der instanceof-Operator genutzt werden kann, um einen einzelnen Typ zu prüfen. Nun können wir diese Fallunterscheidung erweitern, um mehrere Typen zu testen. Nehmen wir an, dass die Zustände von Nap, Workout und Event im XML-Format gespeichert werden sollen. In diesem Kontext kann eine neue Methode die Abbildung auf XML übernehmen:

static String toXml( Object o ) {

  if ( o == null )

    return "<null />";

  if ( o instanceof Nap nap )

    return "<nap about=\"%s\" duration=%s />".formatted( nap.about, nap.duration );

  else if ( o instanceof Workout workout )

    return "<workout about=\"%s\" duration=%s caloriesBurned=%s/> "

        .formatted( workout.about, workout.duration,

                    workout.caloriesBurned );

  else if ( o instanceof Event event )

    return "<event about=\"%s\" duration=%s />".formatted( event.about, event.duration );

  else

    return "<object />";

}

Gerade für solche Abfragen wurde in Java 21 die switch-Anweisung bzw. der switch-Ausdruck erweitert, um die Möglichkeit zu bieten, Typen zu testen und somit eine kaskadierte Typprüfung durchzuführen. Diese Erweiterung wird als Pattern-Matching bei switch (engl. Pattern Matching for switch)[1] bezeichnet, und es handelt sich um das Pendant zum Pattern-Matching bei instanceof.

Die Methode toXml(…) kann folgendermaßen umgeschrieben werden:

static String toXml( Object o ) {

  switch ( o ) {

    case null -> {

      return "<null />";

    }

    case Nap nap -> {

      return "<nap about=\"%s\" duration=%s />".formatted( nap.about, nap.duration );

    }

    case Workout workout -> {

      return "<workout about=\"%s\" duration=%s caloriesBurned=%s/> "

          .formatted( workout.about, workout.duration,

                      workout.caloriesBurned );

    }

    case Event event -> {

      return "<event about=\"%s\" duration=%s />".formatted( event.about, event.duration );

    }

    default -> {

      return "<object />";

    }

  }

}

Die Überprüfung auf null ist mithilfe von case null möglich – eine weitere Erweiterung von Java 21.

Hinweis: Auch in Fällen, in denen eine switch-Anweisung verwendet wird, muss beim Pattern-Matching die Abdeckung vollständig sein. Daher ist in unserem Fall der default-Zweig erforderlich.

Die gewählte Lösung mit der switch-Anweisung ist zwar umsetzbar, doch da jeder switch-Block mit einem return endet, ist auch ein switch-Ausdruck möglich. Eine alternative Schreibeweise für toXml(…) lautet:

static String toXml( Object o ) {

  return switch ( o ) {

    case null -> "<null />";

    case Nap nap ->

        "<nap about=\"%s\" duration=%s />".formatted( nap.about, nap.duration );

    case Workout workout ->

        "<workout about=\"%s\" duration=%s caloriesBurned=%s/> "

            .formatted( workout.about, workout.duration,

                        workout.caloriesBurned );

    case Event event ->

        "<event about=\"%s\" duration=%s />".formatted( event.about, event.duration );

    default -> "<object />";

  };

}

Dominanz

Normalerweise spielt bei einem switch-case die Reihenfolge der case-Blöcke keine Rolle –abgesehen vom Durchfallen, was jedoch bei -> nicht mehr existiert. Beim Pattern-Matching spielt die Reihenfolge sehr wohl eine Rolle und folgendes wäre nicht korrekt:

return switch ( o ) {      

  case null -> "<null />";

  case Event event -> "…";       // ☠ case-Label dominiert

  case Nap nap -> "…";

  …

}

Das case-Label case Event event dominiert das case-Label case Nap nap, daher müssen wir die Reihenfolge berücksichtigen. Im Übrigen gibt es bei der Ausnahmebehandlung einen ähnlichen Fall, Details finden meine Leser im Abschnitt 9.3.5, „Schon gefangen?“

Pattern-Matching mit when Wächter

Bisher haben wir in den case-Blocken nur den Typ überprüft. Es ist jedoch möglich, zusätzliche Bedingungen anzufügen. Hierfür wird nach der Pattern-Variable das Schlüsselwort when verwendet, gefolgt von einer Bedingung, die auf die Pattern-Variable:

Event event = new Nap();

switch ( event ) {

  case Nap nap

  when nap.duration < 10 ->

      System.out.println( "Too brief a sleep, not worth it." );




  case Nap nap

  when nap.duration > 100 -> System.out.println( "That's too long, wake up." );

 

  case Nap nap -> System.out.println( "Recharge and renew with every sleep" );




  case Workout workout -> System.out.println( "Elevate your fitness game");




  default -> {}

}

Hinter dem Schlüsselwort when kann eine Bedingung angegeben werden, die als Wächter (engl. guard) bezeichnet wird. Die Auswertung des case-Blocks erfolgt erst, wenn der Typ übereinstimmt und die Bedingung erfüllt ist. Die Prüfung auf den gleichen Typ kann mehrfach in verschiedenen Blöcken erfolgen. Wir müssen auch hier wieder die Dominanz berücksichtigen. So wäre es falsch, mit case Nap nap -> zu beginnen und erst dahinter ein case Nap nap when … -> zu setzen.

[1] https://openjdk.org/jeps/433

Inselraus: Echtzeit-Java (Real-time Java)

Zwar laufen bei der Java ME Programme auf Geräten mit reduziertem Speicher und eingeschränkter Prozessor-Leistungsfähigkeit, das sagt aber nichts über die Reaktionsfähigkeit der Laufzeitumgebung auf externe Ereignisse aus. Wenn ein Sensor in der Stoßstange einen Aufprall meldet, darf die Laufzeitumgebung keine 20 ms in einer Speicheraufräumaktion festhängen, bevor das Ereignis verarbeitet wird und der Airbag aufgeht. Um diese Lücke zu schließen, wurde schon früh – im Jahr 2001 – von der Java-Community die JSR 1, »Real-time Specification for Java« (kurz RTSJ), definiert (mittlerweile JSR 282 für den Nachfolger RTSJ 1.1.).

Echtzeit-Anwendungen zeichnen sich dadurch aus, dass es eine maximale deterministische Wartezeit gibt, die das System zum Beispiel bei der automatischen Speicherbereinigung blockiert, um etwa auf Änderungen von Sensoren zu reagieren – ein Echtzeitsystem kann eine Antwortzeit garantieren, etwas, was eine normale virtuelle Maschine nicht kann. Denn nicht nur die Zeit für die automatische Speicherbereinigung ist bei normalen Laufzeitumgebungen eher unbestimmt, auch andere Aktionen unbestimmter Dauer kommen dazu: Lädt Java eine Klasse, dann zur Laufzeit. Das kann zu beliebig vielen weiteren Abhängigkeiten und Ladezyklen führen. Bis also eine Methode ausgeführt werden kann, können Hunderte von Klassendateien nötig sein, und das Laden kann unbestimmt lange dauern.

Mit Echtzeitfähigkeiten lassen sich auch Industrieanlagen mit Java steuern und lässt sich Software aus dem Bereich Luft- und Raumfahrt, Medizin, Telekommunikation und Unterhaltungselektronik mit Java realisieren. Dieser Bereich blieb Java lange Zeit verschlossen und bildete eine Domäne von C(++). Damit dies in Java möglich ist, müssen JVM und Betriebssystem zusammenpassen. Während eine herkömmliche JVM auf mehr oder weniger jedem beliebigen Betriebssystem läuft, sind die Anforderungen an Echtzeit-Java strenger. Das Fundament bildet immer ein Betriebssystem mit Echtzeitfähigkeiten (Real-Time Operating System (RTOS)), etwa Solaris 10, Realtime Linux, QNX, OS-9 oder VxWorks. Darauf setzt eine Echtzeit-JVM auf, eine Implementierung der Real-Time-Spezifikation. Real-time Java (RT-Java) unterscheidet sich daher auch in Details, etwa dass Speicherbereiche direkt belegt und freigegeben werden können (Scoped Memory), dass mehr Thread-Prioritäten zur Verfügung stehen oder dass das Scheduling deutlich mehr in der Hand der Entwickler liegt. Die Entwicklung ist anders, findet aber unter den bekannten Werkzeugen wie IDEs, Testtools und Bibliotheken statt. In den letzten Jahren ist es allerdings um Real-time Java ruhig geworden.

Spring Framework 6.0 freigegeben

Jürgen Höller kündigt die neue Version Spring Framework 6 an:

Liebe Spring-Gemeinde,

ich freue mich, Ihnen mitteilen zu können, dass Spring Framework 6.0.0 ab sofort allgemein über Maven Central verfügbar ist! Dies ist der Beginn einer neuen Framework-Generation für das Jahr 2023 und darüber hinaus, die aktuelle und kommende Innovationen in OpenJDK und dem Java-Ökosystem einbezieht. Gleichzeitig haben wir es sorgfältig als unkompliziertes Upgrade von Spring Framework 5.3.x für moderne Laufzeitumgebungen konzipiert.

Als große Überarbeitung des Kern-Frameworks verfügt Spring Framework 6.0 über eine Java 17+-Baseline und einen Wechsel zu Jakarta EE 9+ (im jakarta-Namensraum), wobei der Schwerpunkt auf den kürzlich veröffentlichten Jakarta EE 10-APIs wie Servlet 6.0 und JPA 3.1 liegt. Dies ermöglicht den Zugriff auf die neuesten Webcontainer wie Tomcat 10.1 und die neuesten Persistenzanbieter wie Hibernate ORM 6.1. Bleiben Sie nicht bei Java EE 8 hängen, sondern machen Sie den Sprung zum jakarta-Namensraum, idealerweise direkt auf die Ebene von Jakarta EE 10! Das kommende Spring Boot 3.0.0 Release enthält entsprechende verwaltete Abhängigkeiten für Sie.

Was die Infrastruktur betrifft, so führt 6.0 eine Grundlage für Ahead-Of-Time-Transformationen und die entsprechende AOT-Verarbeitungsunterstützung für Spring-Anwendungskontexte ein. Dies ermöglicht eine erstklassige Unterstützung für native GraalVM-Images mit Spring Boot 3. Sie können auch die virtuellen Threads von Project Loom mit Spring-Anwendungen erforschen und in den Checkpoint-Restore-Ansatz von Project CRaC für schnellere JVM-Starts eintauchen, die beide derzeit noch in der Vorschau sind, aber voraussichtlich zu erstklassigen Features für Spring Framework 6.x-basierte Anwendungen werden.

In Spring Framework 6.0 sind viele weitere Funktionen und Verbesserungen verfügbar, z.B. ein HTTP-Schnittstellen-Client, Unterstützung für RFC 7807-Problemdetails und Micrometer-basierte Beobachtbarkeit für HTTP-Clients. Bitte besuchen Sie unsere Seite What’s New für einen umfassenden Überblick und probieren Sie 6.0.0 frühzeitig aus!

Neu: https://mas.to/@ullenboom

Erster Release Candidate von Spring Boot 3

Details unter https://spring.io/blog/2022/10/20/spring-boot-3-0-0-rc1-available-now,

Mein Spring-Boot-Buch entwickelt sich gut, es gibt keine großen Showstopper. Hibernate 6 ergibt bei einer @ManyToMany Assoziation eine komischer Ausnahme, das was bei Hibernate 5 nicht so war; zum Glück, wenn man von EAGER auf LAZY geht (ist eh Standard), ist das kein Problem mehr.

Kleine Änderungen gegenüber SB 2 betreffen @AutoConfiguration bei eigenen Autokonfigurationen, aber im Allgemeinen ist der Aufwand für eine SB 3-Migration gering, da SB 2.7 verschiedene Sachen vorbereitet hat, etwa spring.factories fällt weg oder der WebSecurityConfigurerAdapter.

Der https://github.com/spring-projects-experimental/spring-boot-migrator kann automatisch migrieren.

Meine aktuellen Spring-Kurse basieren auf SB 2, im nächsten Jahr werde ich diese zu einem großen Kurs zusammenfassen, der dann auf SB 3 basiert.

Spring Boot 2.7.0 ist final, danach kommt 3.0

Siehe https://spring.io/blog/2022/05/19/spring-boot-2-7-0-available-now

Interessant finde ich den Ort der Auto-Konfiguration, der jetzt nicht mehr spring.factories ist, sondern META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Und bei Spring Security, dass der https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html deprecated ist.

Alle andere Neuerungen unter https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes

Nach Spring Boot 2.7 wird es mit Spring Boot 3 weitergehen, Spring Boot 2.8 ist nicht geplant. https://spring.io/projects/spring-boot#support

Die nächsten Tage werde ich meine Spring Kurs https://www.udemy.com/course/dependency-injection-spring-boot-spring-framework/?referralCode=935D6CF5782896E9674B aktualisieren.

Get the latest version of a maven groupId:artifactId via Node.js script

// npm install node-fetch
import fetch from 'node-fetch';
const groupId = "org.apache.maven.plugins";
const artifactId = "maven-compiler-plugin";
const url = `http://search.maven.org/solrsearch/select?q=g:"${groupId}"+AND+a:"${artifactId}"`;
const response = await fetch(url);
const body = await response.json();
const version = body.response.docs[0].latestVersion;
console.log(version);

 

Code Snippets in Java API Doku geplant (JEP 413)

Interessantes Feature, was die Javadoc doch dicker machen könnte. Geplant:

/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet :
 * if (v.isPresent()) {
 *     System.out.println("v: " + v.get());
 * }
 * }
 */

Interessant wird das ganze durch Includes (wie bei Asciidoc):
/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet file="ShowOptional.java" region="example"}
 */
Dinge können zum Beispiel fett hervorgehoben werden:
/**
 * A simple program.
 * {@snippet :
 * class HelloWorld {
 *     public static void main(String... args) {
 *         System.out.println("Hello World!");      // @highlight substring="println"
 *     }
 * }
 * }
 */
Und Texte können ersetzt werden:
/**
 * A simple program.
 * {@snippet :
 * class HelloWorld {
 *     public static void main(String... args) {
 *         System.out.println("Hello World!");  // @replace regex='".*"' replacement="..."
 *     }
 * }
 * }
 */

Ziemlich wild, würdet ihr so was nutzen für eure Javadoc? Ich nutzte bisher immer Testfälle, um eine API zu zeigen und auch zu lernen.

Zum Weiterlesen: https://openjdk.java.net/jeps/413

QueryDSL 5.0.0 verfügbar

Aus den Release-Notes: https://github.com/querydsl/querydsl/releases/tag/QUERYDSL_5_0_0:

This release of QueryDSL targets Java 8 minimally and comes with various improvements to make QueryDSL ready for the modern Java ecosystem.
This version also removes joda-time:joda-time, com.google.guava:guava and com.google.code.findbugs:jsr305 as required runtime dependencies for using QueryDSL.

QueryDSL 5.0 is the long awaited major release after the QueryDSL project was left mostly unmaintained for over two years.

New features

  • #2672 – Various performance and code improvements possible by targeting Java 8 source level.
  • #2672 – Added Fetchable#stream() which returns a Stream<T>.
    Make sure that the returned stream is always closed to free up resources, for example using try-with-resources.
    It does not suffice to rely on a terminating operation on the stream for this (i.e. forEach, collect).
  • #2324 – Removal of Guava as dependency.
    Almost no required transitive dependencies to get started with QueryDSL.
    And no more conflicts with Guava versions required by your other tools or own application.
  • #2025joda-time:joda-time is no longer a required dependency for querydsl-sql.
    By default, the Java 8 date/time API is used for date/time operations.
    The joda-time:joda-time types will still be registered automatically if they are on the classpath.
  • #2215 – MongoDB 4 support through the Document API
  • #2697 – Allow com.querydsl.core.alias.Alias.* to be used on a JRE by relying on ECJ as compiler
  • #2479 – Swap out JSR305 for Jetbrains Annotations.
    Because the Jetbrains Annotations, contrary to the JSR305 annotations, use a Class retention level, Jetbrains Annotations
    does not have to be available at runtime and is not a transitive dependency.
  • #658 – Added JPAExpressions#treat which can be used to generate JPA 2.1 Treated path expressions.
  • #2666 – More descriptive error message when using unsupported query features in JPA.
  • #2106 – Support NullsLast ordering in querydsl-collections.
  • #2404 – Upgrade of JTS / Geolatte in querydsl-spatial
  • #2320 – Make Spatial support available to HibernateDomainExporter and JPADomainExporter.
  • #2612 – Support jakarta.* packages for new Jakarta EE releases (available through thejakarta classifiers for Maven)
  • #1376 – Return typed expression from nullif and coalesce methods.
  • #1828 – Kotlin Codegen support
  • #2798 – Java Record support

Wie ist denn der Einsatz bei euch von QueryDSL? Eine wichtiger Baustein in eurer Unternehmensanwendung?