8 nieuwe JDK features sinds Java 8

Binnen OPEN.satisfaction gebruikten we tot 2021 voor het bouwen van sommige van onze applicaties nog Java 8. Inmiddels zijn ook deze applicaties geüpdatet naar de volgende LTS versie. Hieronder hebben we een aantal nieuwe features uitgelicht, die Java 11 en latere versies, ons te bieden hebben.

1. Records

Sinds Java 14
Records zijn een nieuwe manier om data objecten te definieren, ze zorgen ervoor dat je als ontwikkelaar minder boilerplate code hoeft te maken/handmatig moet genereren, zoals de velden, getters, equals, hashCode en toString methode implementaties.

Het maken van een record doe je door record als het datatype te gebruiken bij het definieren. Bijvoorbeeld:

Een record is immutable. De gegenereerde class is final en extends de Record class. Het genereert ook maar één constructor met alle gedeclareerde variabelen (firstName, lastName en age in ons voorbeeld). Daarnaast volgen de gegenereerde getters de Java Bean standard niet. Getters worden namelijk zo gegenereerd voor het voorbeeld:

  • firstName()
  • lastName()
  • age()

Volgens de Java Bean Standard zou dat zo moeten zijn:

  • getFirstName()
  • getLastName()
  • getAge()

Zoals je in onderstaand voorbeeld kan zien, nemen Records veel boilerplate code weg. Maar als je toch zelf een equals, hashCode en/of toString methode wilt maken kun je deze in de Record class overriden.

Onderstaand hebben we PersonDTO als voorbeeld genomen. Records lenen zich goed voor DTO’s (Data Transfer Objects), waardoor je makkelijk de response van bijvoorbeeld een API kan opvangen in een object.

Bovenstaande voorbeeld zonder records zou er zo uit kunnen zien:

Notities:

  • Sinds Java 14
  • Immutable
  • Zorgen voor minder boilerplate code bij het maken van POJO’s of DTO’s
  • Kan gebruikt worden als vervanger van Lombok
  • Instanties van de records zijn final/immutable
  • Gegenereerde getters en setters volgen de Java Bean Standard niet
  • Overriden van gegenereerde methodes is mogelijk

2. Container Memory Management

Sinds Java 10
Tot en met Java 9 had een Java applicatie geen idee dat het binnen een container draaide en hield geen rekening met memory/cpu limieten gezet vanuit container control groups.

Sinds Java 10 worden limieten standaard gezet aan de hand van de beschikbare info over de container. Met de argumenten -XX:MaxRAMPercentage of -XX:MinRAMPercentage wordt een percentage van het beschikbare van de container gebruikt voor de Java heap size. Met het argument -Xmx wordt exact de hoeveelheid RAM bepaald voor de heap size. Uiteraard mag deze hoeveelheid geheugen niet meer zijn dan beschikbaar is in de container.

Het is nog steeds aan te raden om de limieten voor jouw applicatie zelf bij te stellen aan de hand van de use case.

3. Switch pattern matching

Sinds Java 17
In Java 17 is de switch statement een stuk krachtiger gemaakt waardoor hij voor meer use-cases te gebruiken is. Voor Java 17 moest de selector expression een nummer, string of constante zijn. Voor case label gold ook dat het alleen constanten mochten zijn. Als een variable niet final was, gooide de compiler een error. Bij pattern matching wordt gebruik gemaakt van de `instanceof` operator. Daarmee wordt het type van een object gecheckt en dat maakt casten makkelijker. Het is nu mogelijk om het *type pattern* te gebruiken in een switch statement. De switch selector mag nu van ieder objecttype zijn.


Met het *guarded pattern* is het mogelijk om een boolean expressie op te nemen in een case label. Zo kan conditionele logica worden opgenomen in het case label en kun je voorkomen dat je if condities moet opnemen in het switch statement.


4. Garbage collection

Sinds Java 9-12
De garbage collector is een mechanisme in Java die ervoor zorgt dat alle resources, die je niet meer gebruikt, periodiek worden opgeruimd. Bij garbage collection zijn er 2 aspecten belangrijk:

  • GC throughput: Hoeveel CPU tijd wordt er besteed aan het opruimen?
  • Vertraging van je applicatie door een cycle (Hoeveel tijd kost het tot jouw applicatie weer door mag na een cycle).

Er zijn een aantal Garbage Collectors beschikbaar, maar of je ze kan gebruiken ligt aan de Java versie van je applicatie:

  • Voor Java 9 werd er standaard gebruik gemaakt van de Parallel GC.
  • Vanaf Java 9 is dit de G1 (Garbage First).
  • Vanaf Java 12 is G1 nog steeds de standaard maar zijn Oracle ZGC en OpenJDK Shenandoah erbij gekomen om eventueel te configureren wanneer dit voordeliger is voor jouw applicatie.

Parallel GC

  • Pausing GC: pauzeert tijdelijk de applicatie tijdens het opruimen.
  • Met het verhogen van de heap size zijn er minder vaak opruimcycles nodig  waardoor er minder vertragingen zijn. Maar elke vertraging duurt wel langer, omdat er meer opruimwerk te doen is.

G1

  • “Jack of all trades”: ruimt minder tegelijk op, maar met minder vertraging.
  • Deelt GC pauzes op obv gebruiker gespecificeerd tijd doel.
  • Kortere pauzes? Stel een lager tijd doel in. 
  • Minder CPU gebruik door GC? Stel een hoger tijd doel in.
  • Nadeel: als er erg veel opgeruimd moet worden duurt het langer voordat de kleine brokjes zijn weggewerkt.

Shenandoah

  • Heeft korte vertraging zelfs bij grote heaps.
  • Gebruikt meer CPU tijd dan de Parallel GC, maar de vertraging is sterk verlaagd.
  • Zeer geschikt voor low-latency systemen.
  • Via backports ook te gebruiken in JDK 8 en 11.
  • Garbage collection gebeurt gelijktijdig met de threads van de applicatie, pauzeren is hiervoor niet nodig.

ZGC

  • Weinig vertraging, schaalbaar, makkelijk te gebruiken.
  • Zeer geschikt voor applicaties die veel geheugen gebruiken, maar ook voor applicaties met een kleine heap die voorspelbare en extreem weinig vertraging vragen.
  • Gebruikt ‘concurrent class unloading’ waarmee ongebruikte classes worden opgeruimd zonder de applicatie te pauzeren.

5. Sealed classes

Sinds Java 15
Met Sealed classes kun je er als ontwikkelaar voor kiezen welke classes of interfaces mogen implementeren of extenden, dit doe je door de classes achter de permits te zetten. Classes die daar niet in staan mogen dan ook niet extenden van deze class.
Op deze manier maken we de class Vehicle sealed.

public abstract sealed class Vehicle permits Car, Truck { }

De class Truckmag volgens bovenstaande permits keyword Vehicle extenden, dus kunnen we het volgende doen:

public final class Truck extends Vehicle { }

Het final keyword op Truck wil zeggen dat de class niet nog verder extend kan worden. Als we Truck uit de permits halen, krijgen we vanuit de IDE en bij het compilen van de code een error omdat dit niet mag.

Ook Car mag volgens de permits Vehicle extenden:

public non-sealed class Car extends Vehicle { }

Het keyword non-sealed zorgt ervoor dat elke andere class de Car class weer mag extenden.

Notities:
Een class die een sealed class extend moet: 

  • sealed, non-sealed of final zijn.
    • final zorgt ervoor dat andere classes deze class niet mogen extenden.
    • sealed zorgt ervoor dat andere classes deze class niet mogen extenden behalve door de classes in de `permits`.
    • non-sealed zorgt ervoor dat andere classes deze class mogen extenden. 
  • Direct de class extenden.
  • In dezelfde module zitten als de class zelf (als deze in een “named module” zit of in dezelfde package (als deze in een “unnamed module” zit (zoals bovenstaande classes).

6. String.format optimalisaties

Sinds Java 17
Voor Java 17 werd er altijd gebruik gemaakt van een Regex om de variabelen uit de eerste parameter van de String.format methode gehaald. Sinds Java 17 wordt dit alleen gedaan bij complexere patronen. Bij simpele patronen met bijvoorbeeld een enkele %s wordt de String handmatig ingevuld. Dit zorgt ervoor dat het samenstellen van een String echt flink wordt versneld.

7. var

Sinds Java 11
Als Java developers zijn we gewend aan het strak definieren van het objecttype van onze variabelen. Met de introductie van de var variabele kun je dit overlaten aan Java. Bij complexere objecttypes kan de Javascriptachtig var de code beter leesbaar maken.


8. Text Blocks

Sinds Java 15
Met een text block kan makkelijker een lange string zoals een XML of JSON object worden gedeclareerd. Concatenation is niet meer nodig. Mooi hierbij is dat herkent wordt dat een deel van de string op de nieuwe regel staat, zonder dat je het \n teken in je string hoeft op te nemen. Daar staat wel tegenover dat als een lange string over twee regels toch als een single-line String wilt declareren, je het \ teken aan het einde van de eerste regel moet opnemen.


Honorable mentions

  • String.isBlank() (JDK 11)
  • Set.of(1, 2, 3), List.of(1, 2, 3) en Map.of(“one”, 1, “two”, 2) (JDK 9)
  • Standaard set aan Root CA certificaten in de JDK, zodat SSL/TLS verbindingen out-of-the-box werken (JDK 10)
  • NullPointerException geeft nu precies de naam aan van de variabele die null was (JDK 15)
  • Nieuwe HttpClient (JDK 9)