Misc après Java 8

Java
I111
PO43
Lecture
Diverses nouveautés à partir de Java 8
Auteur
Affiliations

Université de Toulon

LIS UMR CNRS 7020

Date de publication

2024-10-01

Les interfaces en général

A partir de Java 8, il est possible de définir une implantation par défaut dans une interface ainsi que des méthodes statiques.

public interface ActionClass<T> {
    //Depuis Java 9 les méthodes statiques sont autorisées
    static String getInterfaceDescription() {return "Une interface qui impose de définir une action et permet de l'exécuter une liste de <T>";}
    
    void action(T t);
    
    default void action(List<T> list) {
        for(T t:list) action(t);
    }
}
//Une classe qui affiche une String en majuscule et s'applique automatiquement à une liste de String
public class UpperCaser implements ActionClass<String> {
    public void action(String s) {System.out.println(s.toUpperCase());}
}
//Une classe qui affiche la concaténation du nom et du prénom d'une personne et s'applique automatiquement à une liste de Personne
public class PersonFullname implements ActionClass<Person> {
    public void action(Person p) {System.out.println(p.prenom()+" "+p.nom());}
}

//La factory .of() est apparue dans Java9 pour créer des Collections non mutables.
new UpperCaser().action(List.of("abc","def"));

new PersonFullname().action(personnes);

System.out.println(ActionClass.getInterfaceDescription());
ABC
DEF
Pierre Durand
Marie Durand
Albert Martin
Une interface qui impose de définir une action et permet de l'exécuter une liste de <T>
Albert Martin
Une interface qui impose de définir une action et permet de l'exécuter une liste de <T>

NPE et Optional<T>

Pour traiter plus simplement les possibles tests des cas de valeurs nulles, Java8 introduit le type Optional, un conteneur qui fournit des méthodes utilitaires dédiées.

La factory of vérifie que la référence n’est pas nulle et provoque une exception à la création sinon.

La méthode get() retourne la valeur. Depuis Java 9, on préfére l’utilise de orElseThrow() qui émet une NoSuchElementException en l’abscence de valeur.

String str1 = "value1";
Optional<String> optional1 = Optional.of(str1);
optional1.orElseThrow();
value1
String str1Bis = null;
Optional<String> optional1 = Optional.of(str1Bis);
EvalException: null
Execution exception
---------------------------------------------------------------------------

java.lang.NullPointerException: null
    at java.base/java.util.Objects.requireNonNull(Objects.java:233)
    at java.base/java.util.Optional.of(Optional.java:113)
    at .(#69:1)

Il est possible de manipuler des références éventuellement nulles avec la factory ofNullable().

String str2 = "value2";
Optional<String> optional2 = Optional.ofNullable(str2);
optional2.orElseThrow();
value2
String str3 = null;
Optional<String> optional3 = Optional.ofNullable(str3);
optional3.orElseThrow();
EvalException: No value present
Execution exception
---------------------------------------------------------------------------

java.util.NoSuchElementException: No value present
    at java.base/java.util.Optional.orElseThrow(Optional.java:377)
    at .(#105:1)

Dans ce cas OrElse() est une alternative à pour obtenir une valeur par défault en cas de valeur nulle au lieu d’une Exception.

String str3Bis = null;
Optional<String> optional3Bis = Optional.ofNullable(str3Bis);
optional3Bis.orElse("defaultValue3");
defaultValue3

OrElseGet(Supplier s) est une variante plus efficace si le calcul de la valeur par défaut est complexe car alors la fonction n’est évaluée qu’en cas de valeur nulle.

Dans l’exemple ci dessous en cas de valeur nulle, une entrée alétoire du tableau est retournée.

String str4 = null;
//String str4 = "value3"; 
String[] values={"a1","b2","c3"};

Optional<String> optional4 = Optional.ofNullable(str4);

optional4.orElseGet( () -> values[new Random().nextInt(values.length)] );
c3

Modularisation

Java 9 introduit la notion de module. Un module regroupe des classes, permet de définir des dépendances entre modules et défini une API publique. Le JDK devient modulaire et les programmes utilisateurs peuvent l’être cela permet d’executer des programmes en consommant moins de mémoire. Les classes internes du JDK ne sont plus accessibles depuis l’application pour améliorer la sécurité.

Plus de détails : https://openjdk.java.net/projects/jigsaw/quick-start

Process

L’accès aux informations des processus systèmes est amélioré. Les examples suivants illustre l’accès au processus de la JVM et à tous ceux du système.

ProcessHandle processHandle = ProcessHandle.current();
ProcessHandle.Info processInfo = processHandle.info();

System.out.println("PID: " + processHandle.pid());
System.out.println("Arguments: " + processInfo.arguments());
System.out.println("Command: " + processInfo.command());
System.out.println("Instant: " + processInfo.startInstant());
System.out.println("Total CPU duration: " + processInfo.totalCpuDuration());
System.out.println("User: " + processInfo.user());
PID: 39089
Arguments: Optional[[Ljava.lang.String;@1a12b9f7]
Command: Optional[/home/jovyan/.sdkman/candidates/java/21.0.4-tem/bin/java]
Instant: Optional[2024-10-01T16:25:39.550Z]
Total CPU duration: Optional[PT22.79S]
User: Optional[jovyan]
Instant: Optional[2024-05-23T21:04:02.890Z]
Total CPU duration: Optional[PT6.67S]
User: Optional[jovyan]

import java.util.stream.Stream;

Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses();

liveProcesses.filter(ProcessHandle::isAlive).limit(3)
        .forEach(ph -> {
            System.out.println("PID: " + ph.pid());
            System.out.println("  Start: " + ph.info().startInstant());
            System.out.println("  User: " + ph.info().user());
        });
PID: 1
  Start: Optional[2024-09-30T15:21:34.640Z]
  User: Optional[root]
PID: 6
  Start: Optional[2024-09-30T15:21:34.690Z]
  User: Optional[root]
PID: 48
  Start: Optional[2024-09-30T15:21:36.300Z]
  User: Optional[jovyan]
  User: Optional[root]
PID: 6
  Start: Optional[2024-05-23T20:48:39.900Z]
  User: Optional[root]
PID: 50
  Start: Optional[2024-05-23T20:48:45.160Z]
  User: Optional[jovyan]

//TODO : ProcessBuilder (Since Java 7)

Inférence du type des variables locales

Depuis Java 10, le type des variables locales avec initialisation peut être inféré à la compilation avec le mot clé var.

var i = 3;
var myMap = new HashMap<Integer,String>();

myMap.put(1,"A");
myMap.put(2,"B");
myMap;
{1=A, 2=B}

Depuis Java 11, c’est possible dans les lambdas. Le type des paramètre est déjà inféré mais cela permet d’associer des annotations sans avoir à spécifier le type.

public @interface MyAnnotation { }

import java.util.function.BiFunction;
BiFunction<String,String,String> f1 = (s1, s2) -> s1 + s2;

BiFunction<String,String,String> f2 = (@MyAnnotation var s1, @MyAnnotation var s2) -> s1 + s2;

Instance Of

Depuis Java 12 en preview, l’opérateur instance of permet de faire implicement un cast en cas de succès.

Object obj = new Person("pierre.durand@a.fr","Pierre", "Durand", 20);

if (obj instanceof Person p) {
    System.out.println(p.prenom());
}
Pierre

Text blocks

Depuis Java 13, il est possible d’utiliser des litéraux multilignes pour les String.

String person1Json="""
{
    "fistname" : "%s",
    "lastname" : "%s",
    "age" : %d,
    "description" : "lorem ipsum.............\
..................\
..................lorem ipsum"
}    
""".formatted("Pierre", "Durand", 12);
    
person1Json;
{
    "fistname" : "Pierre",
    "lastname" : "Durand",
    "age" : 12,
    "description" : "lorem ipsum.................................................lorem ipsum"
}

Records

Les record (depuis Java 14/15) permettent de définir très simplement des classes non mutables avec constructeurs, accesseurs (sans le préfixe get), toString().

public record Dog(int id, String name){ };

Dog d1 = new Dog(1, "Rex");
Dog d2 = new Dog(2, "Médor");
Dog d3 = new Dog(1, "Rex");

System.out.println(d1+"-> nom:"+d1.name());
Dog[id=1, name=Rex]-> nom:Rex

ainsi qu’une implantation de equals et hashcode (égalités de tous les composants). Il sont typement très utilisés pour les design pattern comme le DTO.

import java.util.stream.*;
System.out.println(Stream.of(d1,d2,d3).collect(Collectors.toCollection(HashSet::new)).toString());            
[Dog[id=2, name=Médor], Dog[id=1, name=Rex]]

Il est possible de redéfinir ou de surcharger le constructeur et les accesseurs/modificateurs et d’ajouter des variables et méthodes de classes mais pas de variable d’instance.

public record User(int id, String firstname){ 
        public User {
            if(id < 0) { throw new IllegalArgumentException("Id has to be greater than 0.");
        }
    }
};

new User(-1,"Pierre");
EvalException: Id has to be greater than 0.
Execution exception
---------------------------------------------------------------------------

java.lang.IllegalArgumentException: Id has to be greater than 0.
    at User.<init>(#130:7)
    at .(#132:1)

Sealed and Hidden classes

public abstract sealed class A
    permits B, C {
}

public final class B extends A  { };
public non-sealed class C extends A  { };

Switch

A partir de Java 17 le switch peut évaluer une expression et retourner sa valeur.

public enum Day { SUNDAY, MONDAY, TUESDAY,
    WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }

Day day = Day.WEDNESDAY;    
//Day day = Day.TUESDAY;    

int l = 
        switch (day) {
            case MONDAY, FRIDAY, SUNDAY -> 6;
            case TUESDAY                -> 7;
            case THURSDAY, SATURDAY     -> 8;
            case WEDNESDAY              -> 9;
            default -> throw new IllegalStateException("Invalid day: " + day);
        };

l;
9

et utiliser des Pattern de classes.

record Dog(int id, String name) {};
record User(int id, String firstname) {};

Dog d1 = new Dog(1, "Rex");

User u1= new User(1,"Pierre");

public String check(Object obj) {
    return switch (obj) {
                case User u -> "I'm a user named "+u.firstname();
                case Dog d -> "I'm a dog named "+d.name();
                case null -> "NULL !";
                default -> "A simple object: "+obj;
    };
}

System.out.println("-->"+check(d1));
System.out.println("-->"+check(u1));
System.out.println("-->"+check(null));
System.out.println("-->"+check(new Integer(12)));
-->I'm a dog named Rex
-->I'm a user named Pierre
-->NULL !
-->A simple object: 12

Record Pattern

En preview dans Java 19.

public static void printName(Object o) {
    if (o instanceof Dog(var id, var name))
        System.out.println("Dog %d name is %s.".formatted(id,name));
    else 
        if (o instanceof User(var id, var firstname))
            System.out.println("User %d firstname is %s.".formatted(id,firstname));
}
    
printName(new Dog(3,"Félix"));
printName(new User(1,"Pierre"));
Dog 3 name is Félix.
User 1 firstname is Pierre.

Réutilisation