Cours 1 - Évolution du langage Java - Langages de la JVM II
Par exemple, en utilisant des constantes statiques
public class ElèveIngénieur {
public static final int ETUDIANT = 0;
public static final int APPRENTI = 1;
public static final int FORMATION_CONTINUE = 2;
}
mais alors le type lui-même n'est pas contraint
classes d'énumération Java 1.5
enum
Java 1.5enum
enum MoyenPaiement {
ESPECES, CHEQUE, CARTE
}
==
(il n'en existe qu'une copie)MoyenPaiement moyenPaiement = ...;
// ...
if (moyenPaiement == MoyenPaiement.ESPECES) { ... }
switch
, sans nécessité de mentionner le type d'enum
switch (moyenPaiement) {
case ESPECES -> ...
}
java.lang.Enum
. Il est possible de récupérer le nom d'une constante d'énumération (toString()
❶), de trouver une constante d'énumération à partir de son nom (valueOf()
❷), et de trouver toutes les valeurs possibles d'une énumération (values()
❸).MoyenPaiement.CARTE.toString(); // renvoie "CARTE" ❶
MoyenPaiement mp;
mp = (MoyenPaiement) Enum.valueOf(MoyenPaiement.class, "ESPECES"); // ❷
mp = MoyenPaiement.valueOf("ESPECES"); // ❷
for (MoyenPaiement moyen : MoyenPaiement.values()) // ❸
System.out.print(moyen + " "); // ESPECES CHEQUE CARTE
public enum Taille {
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("X");
private String lettre; // ❶
private Taille(String lettre) { this.lettre = lettre; } // ❸
public String getLettre() { return lettre; } // ❷
}
// ...
Scanner in = new Scanner(System.in);
System.out.println("Taille (SMALL, MEDIUM, LARGE, EXTRA) : ");
String input = in.next().toUpperCase();
Taille taille = (Taille)Enum.valueOf(Taille.class, input); // ...
System.out.println(taille.toString() + " (" + taille.getLettre() + ")");
java.lang.Enum
, donc uniquement d'ellepublic enum Jour {
LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE
}
// ...
class TestEnum {
Jour jour;
public TestEnum(Jour jour) { this.jour = jour; }
public String leDireEnVrai() {
return switch (jour) {
case LUNDI : yield "Les lundi sont durs.";
case VENDREDI : yield "Les vendredi sont mieux.";
case SAMEDI, DIMANCHE : yield "Rien ne remplace les week-ends.";
default : yield "Les autres jours sont comme ci, comme ça.";
};
}
public static void main(String[] args) {
TestEnum jour1 = new TestEnum(Jour.LUNDI);
System.out.println(jour1.leDireEnVrai());
// ...
}
}
classes internes Java 1.1
Les classes internes (nested classes)
private
)public class ClasseExterne {
// ...
class ClasseInterne {
// ...
}
}
$
pour séparer les noms de classes externes et internes (ex. ClasseExterne\$ClasseInterne.class
❶).public class ClasseExterne {
public ClasseExterne() { }
class ClasseInterne {
public ClasseInterne() { }
public void faitQuelqueChoseInterne() { }
}
public void faitQuelqueChoseExterne() { }
}
javap
:$ javap -private ClasseExterne\$ClasseInterne # ❶
Compiled from "ClasseExterne.java"
class ClasseExterne$ClasseInterne {
final ClasseExterne this$0; ❷
public ClasseExterne$ClasseInterne(ClasseExterne);
public void faitQuelqueChoseInterne();
}
static
(inner classes)ClasseInterne
n'existe pas indépendamment de ClasseExterne
: la portée d'une classe interne est limitée à celle de sa classe englobanteClasseInterne
reçoit implicitement une référence à l'objet qui l'a créé : ainsi, une méthode de ClasseInterne
a directement accès aux membres de l'objet externe créateur de type ClasseExterne
, comme depuis le contexte de ClasseExterne
java.util.Iterator<Integer>
) dans une classe interne ❶ : celle-ci devient disponible pour les besoins internes de sa classe englobante ❷.public class Test {
// ...
public void afficheIndicesPairs() {
IterateurPair iterateur = new IterateurPair(); // ❷
while (iterateur.hasNext())
System.out.print(iterateur.next() + " ");
}
private class IterateurPair implements java.util.Iterator<Integer> { // ❶
private int indiceSuivant = 0;
@Override public boolean hasNext() {
return (indiceSuivant <= TAILLE - 1);
}
@Override public Integer next() throws NoSuchElementException {
if (!hasNext()) throw new NoSuchElementException();
Integer valeur = Integer.valueOf(tab[indiceSuivant]);
indiceSuivant += 2;
return valeur;
}
}
}
public class ClasseExterne {
private boolean beep;
// ...
public void start(final String message) {
class AfficheurTemps implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println(message + new Date());
if (beep) Toolkit.getDefaultToolkit.beep();
}
}
ActionListener listener = new AfficheurTemps();
Timer timer = new Timer(interval, listener);
timer.start();
}
// ...
}
new SuperType(paramètres de construction) {
données et méthodes de la classe interne
}
label
❶ appartient à la classe englobante et est donc directement accessible; cette classe particulière a donc bien un fonctionnement très spécifique à sa classe englobante), ex.:final TextField tfValeurNum = new TextField() {
@Override public void replaceText(int start, int end, String text) {
if (!text.matches("[a-z,A-Z]")) {
super.replaceText(start, end, text);
}
else label.setText("Enter a numeric value"); // ❶
}
};
static
visible comme sous-classe depuis l'extérieurstatic
ne peut accéder à l'état d'un objet et ne peut donc accéder qu'aux membres static
de sa classe englobante (ou via un objet de ce type)-
static
par inner classesstatic MinMax.PairMinMax
❶ utilisée comme type spécifique de retour par sa classe englobante ❷.public class MinMax {
public static class PaireMinMax { // ❶
private double min, max;
public PaireMinMax(double min, double max) { this.min = min; this.max = max; }
public double getMin() { return min; }
public double getMax() { return max; }
}
public static PairMinMax minmax(double[] values) {
double min = Double.MAX_VALUE, max = Double.MIN_VALUE;
for (double value : values) {
if (min > value) min = value;
if (max < value) max = value;
}
return new PairMinMax(min, max); // ❷
}
public static void main(String[] args) {
double[] valeurs = ...;
MinMax.PaireMinMax résultat = MinMax.minmax(valeurs);
System.out.println("min = " + résultat.getMin() + ...
}
}
static
des API standardclasses d'enregistrement (record
) Java 16
record
Java 16record
déclare des champs (syntaxe spéciale pour une liste de composants (component list)), et laisse au compilateur la création de code pouvant être déduit de sa définitiontoString()
❶, hashCode()
❷, equals()
❸, accesseurs (getters) (qui portent directement le nom du champ) ❹, constructeur public ❺final
et sans accesseurs (setters))
public record Achat(int nombre,
PaireDevises paire,
double prix,
LocalDateTime dateOrdre) {}
$ javap Achat.class
Compiled from "Achat.java"
public final class Achat extends java.lang.Record {
public Achat(int, PairDevises, double, java.time.LocalDateTime); ❺
public java.lang.String toString(); ❶
public final int hashCode(); ❷
public final boolean equals(java.lang.Object); ❸
public int nombre(); ❹
public PaireDevises paire(); ❹
public double prix(); ❹
public java.time.LocalDateTime dateOrdre(); ❹
}
Unrecord
est un transporteur d'un ensemble fixe de données, qui correspondent à un champ final
pour lequel une méthode de consultation (getter) portant le nom du champ est créée
Points d'implémentation
record
est final
(donc pas de sous-classes possibles)java.lang.Record
est la classe parente (implicite) de tous les record
equals()
, hashCode()
et toString()
avecabstract
record
, mais possibilité d'implémenter des interfacesIl est possible d'ajouter des méthodes, constructeurs et champs static
à un record
ageActuel()
pour une classe Personne
(par ailleurs immuable)Un record
peut être paramétré : record Paire<T>(T x, T y) {}
record
.
record
public Achat {
if (nombre < 1) {
throw new IllegalArgumentException("La quantité doit être positive");
}
// ...
}
The formal parameters of a compact constructor of a record class are implicitly declared. They are given by the derived formal parameter list of the record class. The intention of a compact constructor declaration is that only validation and/or normalization code need be given in the body of the canonical constructor; the remaining initialization code is supplied by the compiler.
class Livre {
private String titre;
private int édition;
// ...
public Livre(String titre, int édition) {
if (titre == null) throw new NullPointerException("titre");
this.titre = titre;
this.édition = édition;
}
public Livre withEdition(int édition) { // ❶
return new Livre(titre, édition);
}
// ...
}
Livre nouvelleEdition = ancienneEdition.withEdition(2).withFormat(FormatLivre.BROCHE).withISBN("...");
public Livre(String titre, int édition) { ... }
public Livre(String titre, int édition, FormatLivre livre) { ... }
static
!) la construction de nouvelles instances
static
de la classe qui retourne le builder ❷public class Livre {
private Livre() { } // ❹
public static LivreBuilder builder() { // ❷
return new LivreBuilder();
}
public static class LivreBuilder {
private String titre;
private int édition;
// ...
LivreBuilder() { }
public LivreBuilder titre(String titre) { // ❶
this.titre = titre;
return this;
}
// ...
public Livre build() { // ❸
if (titre == null || titre.isBlank()) throw new IllegalArgumentException();
Livre livre = new Livre();
livre.titre = titre;
livre.édition = édition;
// ...
return livre;
}
}
}
build()
Livre nouveauLivre = Livre.builder()
.titre("Le bug humain")
.édition(1)
.build();
types scellés (sealed
) Java 17
final
)Pas vraiment, car rien n'empêche la création de classes dans un package quelconque (hormis les packages réservés)
Possibilité de limiter les sous-types possibles d'un type scellé (mot-clépermits
)
Exemple d'interface scellée autorisant uniquement 2 records
particuliers
public sealed interface Achat permits AchatPrixMarché, AchatPrixLimité {
int nombre();
PairDevises paire();
LocalDateTime dateOrdre();
}
public record AchatPrixMarché(int nombre, PaireDevises paire, LocalDateTime dateOrdre) implements Achat { ... }
public record AchatPrixLimité(int nombre, PaireDevises paire, LocalDateTime dateOrdre, double prix) implements Achat { ... }
final
enum
appliqué aux types plutôt qu'aux instancesle multicatch des exceptions
catch (Exception1 | Exception2 | Exception3 exception) { ... }
InscritPolytech.Etudiant
et InscritPolytech.Apprenti
public abstract sealed class InscritPolytech {
// ...
public static final class Etudiant extends InscritPolytech {
// ...
}
public static final class Apprenti extends InscritPolytech {
// ...
}
}
switch
switch
: commodité pour le/la développeuse, meilleure lisibilité et sécuritécase L:
' , qui autorisent des branchements en cascade, peuvent être utilement remplacés par des branchements 'case L ->
' Java 17
case L:
' requiert l'utilisation de break
(sortie de l'instruction) ou de yield
(retour d'une valeur pour l'expression)case L1, L2 ->
' permet d'énumérer les cas ❶, et les associe soit à une expression, une propagation d'exception (throw
) ou un bloc d'instructions (terminé par yield
pour l'expression) ❷boolean estUneVoyelle = switch (lettre) {
case 'a', 'e', 'i', 'o', 'u', 'y' -> true; // ❶
default -> { // ❷
System.err.println("On a testé une non-voyelle : " + lettre);
yield false;
}
}
switch
doivent couvrir l'ensemble des cas possibles, et donc requiert une clause default
switch
doit donc soit retourner une valeur du type de l'expression pour tous cas, soit propager une exceptionenum
), le compilateur peut vérifier l'exhaustivité de la couverture des casString messageDHumeur (jour) {
case LUNDI -> "Les lundi sont durs.";
case MARDI -> "Le mardi c'est chaud.";
case MERCREDI -> "Le mercredi ça commence à tirer...";
case JEUDI -> "Les jeudis sont interminables...";
case VENDREDI -> "Les vendredi sont plutôt pas mal.";
case SAMEDI, DIMANCHE ->"Rien ne remplace les week-ends.";
};
switch
switch
Java 21 (JEP 441)
switch
) peut être n'importe quel type de référence ou int
switch
peuvent tester si leur expression de sélection correspond à un patron (possiblement "null
")
Object objet = ...;
switch (objet) {
case null -> System.out.println("valeur null");
case String s -> System.out.println("chaîne de caractère : " + s);
default -> System.out.println("un objet autre que String");
}
public static double calculPérimètre(Forme forme) throws IllegalArgumentException {
return switch (forme) {
case Rectangle r -> 2 * r.longueur() + 2 * r.largeur();
case Cercle c -> 2 * c.rayon() * Math.PI;
default -> throw new IllegalArgumentException("périmètre non calculable pour cette forme (si non null)");
};
}
when
pour spécifier des sous-cas (avec règles de précédence) périmètre = switch (forme) {
case Rectangle r when r.estBienFormé() -> 2 * r.longueur() + 2 * r.largeur();
case Rectangle r -> throw new IllegalArgumentException("impossible de calculer le périmètre de ce rectangle");
default -> throw new IllegalArgumentException("périmètre non calculable pour cette forme (si non null)");
};
record
), avec décomposition de la classe, possibilité d'inférence de type (var
) et de patron anonyme (_
)record Rectangle(double largeur, double hauteur, Couleur couleur) { }
static double calculeAire(Forme forme) {
return switch (forme) {
case Rectangle(double largeur, double hauteur, _) -> largeur * hauteur;
default -> throw new IllegalStateException("calcul d'aire inconnu pour : " + forme);
}
Suivi des évolutions du langage à l'aide de site tels que Java Almanach
Possibilité de comparer entre deux versions des APIs (ex. Java 17 (LTS) vers Java 21 (LTS))
Dans le cadre professionnel, on n'utilise typiquement pas des versions non LTS (Long-Term Support) en production
On peut toutefois installer localement un nouveau JDK pour expérimenter avec ces nouvelles possibilités (fonctionnalités, performance, correction de bugs, incompatibilités pour montée de version, etc.)
On peut aussi expérimenter en ligne avec The Java Playgound