Développons en Java
v 2.40   Copyright (C) 1999-2023 .   
2. Les notions et techniques de base en Java 4. La programmation orientée objet Imprimer Index Index avec sommaire Télécharger le PDF

 

3. La syntaxe et les éléments de bases de Java

 

chapitre    3

 

Niveau : niveau 1 Fondamental 

 

Ce chapitre détaille la syntaxe et les éléments de bases du langage Java. Cette syntaxe est largement inspirée de celle du langage C.

Ce chapitre contient plusieurs sections :

 

3.1. Les règles de base

Java est sensible à la casse.

Les blocs de code sont encadrés par des accolades. Chaque instruction se termine par un caractère ';' (point-virgule).

Une instruction peut tenir sur plusieurs lignes :

Exemple :
char
code
=
'D';

L'indentation est ignorée du compilateur mais elle permet une meilleure compréhension du code par le programmeur.

 

3.2. Les mots réservés du langage Java

Java 9 définit 54 mots réservés qui ne peuvent pas être utilisés comme identifiant.

Historiquement, les mots réservés (reserved words) peuvent être regroupés en deux catégories :

Les mots clés sont des mots réservés utilisés dans le code source Java en ayant un rôle particulier dans la syntaxe du code.

abstract continue for new switch
assert (Java 1.4) default goto package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum (Java 5) instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp (Java 1.2 à 16) volatile
const float native super while
_ (Java 9)        

Les mots clé const et goto sont réservés mais ne sont pas utilisés actuellement.

A partir de Java 9, le mot-clé _ (le caractère underscore) est non utilisé pour le moment mais il est réservé pour une éventuelle utilisation future dans les déclarations de certaines variables.

A partir de Java 17, le mot-clé réservé strictfp est obsolète et ne devrait plus être utilisé dans le code.

Plusieurs séquences sont fréquemment considérées à tort comme des mots clés du langage et sont en fait des valeurs litérales :

Différents types de mots-clés sont définis dans le langage Java en fonction de la version utilisée :

Le langage Java définit plusieurs mots-clés contextuels (pour certains nommés initialement mots-clés restreints ou identifiants restreints) depuis Java 9 :

exports

module

non-sealed (Java 17)

open

opens

permits (Java 17)

provides

record (Java 16)

requires

sealed (Java 17)

to

transitive

uses

var (Java 10)

when (Java 20)

with

yield (Java 14)

     

Le but de ses mots-clés contextuels est de pouvoir ajouter des mots-clés qui sont considérés comme tels dans certains contextes et dans les autres cas sont considérés comme des identifiants légaux pour maintenir la compatibilité.

Une séquence de caractères correspondant à un mot-clé contextuel n'est pas traitée comme un mot-clé si une partie quelconque de la séquence peut être combinée avec les caractères immédiatement précédents ou suivants pour former un jeton différent.

Deux mots-clés doivent être séparés par un caractère d'espacement ou un commentaire.

Version de Java

Evolution des mots-clés

Java 9

10 mots-clés restreints (restricted keywords) sont introduits : open, module, requires, transitive, exports, opens, to, uses, provides et with.

Ils sont considérés comme des mots-clés uniquement dans le contexte de la définition d'un descripteur de module.

Dans tous les autres contextes, ils sont considérés comme des identifiants, pour assurer la compatibilité avec le code écrit avant l'introduction des mots-clés restreints.

Il y a une exception : immédiatement à droite de la séquence de caractères requires dans la déclaration d'un descripteur de module, la séquence de caractères transitive est considérée comme un mot-clé à moins qu'elle ne soit suivie d'un séparateur, auquel cas elle est considérée comme un identifiant.

Java 10

La séquence de caractère var est considérée comme un identifiant restreint : var est considéré comme un mot-clé lorsque qu'il est utilisé pour la déclaration d'une variable locale en demandant au compilateur d'inférer le type en fonction de la valeur d'initialisation.

Java 11

var est aussi considéré comme un mot-clé lorsque qu'il est utilisé pour la déclaration un paramètre d'une expression lambda en demandant au compilateur d'inférer le type en fonction de la signature de l'unique méthode de l'interface fonctionnelle.

Java 14

La séquence de caractère yield est considérée comme un identifiant restreint : yield est considéré comme un mot-clé lorsque qu'il est utilisé dans une instruction swith comme une expression.

Java 15

La séquence de caractère record est considérée comme un identifiant restreint : record est considéré comme un mot-clé lorsque qu'il est utilisé pour déclarer un type qui encapsule des données de manière immuable.

Java 17

La notion de mot-clé contextuel (contextual keyword) remplace les notions antérieures d'identifiant restreint (restricted identifier) et de mot-clé restreint (restricted keyword) dans la JLS.

Les mots-clés contextuels sealed, non-sealed et permits sont ajoutés. A noter que le mot-clé contextuel non-sealed est le premier mot clé à utiliser un nom composé avec un trait d'union. L'utilisation d'un nom composé avec un trait d'union permet d'ajouter de nouveaux mot clés tout en garantissant la compatibilité car un identifiant valide en Java ne peut pas contenir un trait d'union.


Le nom d'un type (type identifier) ne peut pas être permits, record, sealed, var ou yield.

Exemple :
public class var {
}

Résultat :
C:\java>javac -version
javac 10.0.2
 
C:\java>javac var.java
var.java:1: error: 'var' not allowed here
public class var {
             ^
  as of release 10, 'var' is a restricted local variable type and cannot be used for type 
declarations
1 error
 
C:\java>

Exemple :
public class yield {
}

En Java 13, le compilateur émet un avertissement si un type se nomme yield.

Résultat :
C:\java>javac -version
javac 13
 
C:\java>javac yield.java
yield.java:1: warning: 'yield' may become a restricted type name in a future release and 
may be unusable for type declarations or as the element type of an array
public class yield {
             ^
1 warning
 
C:\java>

En Java 13, le compilateur émet un avertissement lors de l'utilisation de yield comme identifiant.

Résultat :
C:\java>javac -version
javac 13
 
C:\java>javac TestYield.java
TestYield.java:7: warning: 'yield' may become a restricted identifier in a future release
    yield();
    ^
  (to invoke a method called yield, qualify the yield with a receiver or type name)
1 warning
 
C:\java>

A partir de Java 14, le compilateur émet une erreur si un type se nomme yield.

Résultat :
C:\java>javac -version
javac 14
 
C:\java>javac yield.java
yield.java:1: error: 'yield' not allowed here
public class yield {
             ^
  as of release 13, 'yield' is a restricted type name and cannot be used for type declarations
1 error
 
C:\java>

Jusqu'à Java 12, il est possible d'invoquer une méthode nommée yield sans la qualifier.

Exemple :
public class TestYield {
 
  public void yield() {
  }
 
  public void traiter() {
    yield();
  }
}

Résultat :
C:\java>javac -version
javac 12
 
C:\java>javac TestYield.java
 
C:\java>

A partir de Java 14, le compilateur émet une erreur car il faut obligatoirement qualifier l'invocation d'une méthode nommée yield() de façon à se distinguer d'une utilisation de yield comme instruction.

Résultat :
C:\java>javac -version
javac 14
 
C:\java>javac TestYield.java
TestYield.java:7: error: invalid use of a restricted identifier 'yield'
    yield();
    ^
  (to invoke a method called yield, qualify the yield with a receiver or type name)
1 error
 
C:\java>

record a une signification particulière dans une déclaration d'une classe record.

Jusqu'à Java 12, un type peut se nommer record.

Exemple ( code Java 12 ) :
public class record {
}

En Java 14 et 15, le compilateur émet un avertissement si un type se nomme record.

Résultat :
C:\java>javac -version
javac 14
 
C:\java>javac record.java
record.java:1: warning: 'record' may become a restricted type name in a future release and
 may be unusable for type declarations or as the element type of an array
public class record {
             ^
1 warning
 
C:\java>

A partir de Java 16, le compilateur émet une erreur si un type se nomme record.

Résultat :
C:\java>javac -version
javac 16.0.1
 
C:\java>javac record.java
record.java:1: error: 'record' not allowed here
public class record {
             ^
  as of release 14, 'record' is a restricted type name and cannot be used for type declarations
1 error
 
C:\java>

 

3.3. Les identifiants

Les identifiants (identifiers) sont utilisés pour nommer des éléments dans du code source Java notamment les variables, les classes, les méthodes, les paramètres, les packages, les interfaces, les énumérations, les étiquettes, les modules, ... Les identifiants permettent aux développeurs d'utiliser un élément ailleurs dans le code.

Exemple :
public class MaClasse {
  public static void main(String[] args) {
    int valeur = 10;
  }
}

Dans l'exemple ci-dessus, plusieurs identifiants sont utilisés dans le code source :

Les spécifications de Java imposent des règles strictes pour définir un identifiant valide.

Un identifiant est une séquence d'un ou plusieurs caractères (lettres et chiffres) dont le premier est obligatoirement une lettre.

La première lettre est un caractère Unicode pour laquelle la valeur passée en paramètre de la méthode isJavaIdentifierStart() de la classe Character renvoie true. Le premier caractère ne peut donc pas être un chiffre.

Une lettre peut être entre-autre :

La quasi-totalité des caractères Unicode peuvent être utilisés ce qui permet d'écrire des identifiants dans toutes les langues. Cependant plusieurs caractères ne peuvent pas être utilisés dans un identifiant : c'est notamment le cas des caractères qui ont une utilité dans le langage comme par exemple un espace, une tabulation, les caractères #, @, !, -, *, + , /, %, (, ), ...

Les identifiants Java sont sensibles à la casse.

La taille d'un identifiant n'est pas limitée mais pour des raisons de lisibilité, il est préférable qu'ils ne soient pas trop petit ni trop grand.

Remarque : depuis Java 9, l'identifiant composé uniquement d'un caractère underscore n'est plus valide car _ est devenu un mot clé du langage.

Un identifiant ne peut pas être :

Les identifiants utilisés pour un type doivent respecter les règles générales de définition d'un identifiant. Depuis Java 10, l'identifiant d'un type ne peut pas être var qui est utilisé de manière particulière dans le contexte de la définition d'une variable locale.

Exemple d'identifiants valides :

_ (jusqu'à Java 8)

a

a1

_$

mavariable

MaVariable

MAVARIABLE

maVariable

ma_variable

mavariable1

ma1variable

mavariable_

_mavariable

ma$variable1

_maVariable

$maVariable

ma_variable_initialisée

identifiant en cyrilique

   

variable_avec_un_nom_tres_long_avec_chaque_mot_separe_par_un_underscore


Exemple d'identifiants invalides :

Identifiant invalide

Raison pour laquelle il est invalide

1aabbb

Commence par un chiffre

aa-bb

Contient un caractère moins

aa bb

Contient un espace

_

Le caractère underscore est devenu un mot clé en Java 9

aa*bb

Contient un caractère *

()_

Contient des parenthèses

true

Est une valeur littérale réservée

new

Est un mot clé réservé du langage

aa+bb

Contient un caractère +

aa&bb

Contient un caractère &

aa@bb

Contient un caractère @

aa#bb

Contient un caractère #


Remarque : il est important de prêter une attention particulière lors de l'utilisation d'un identifiant pour que celui-ci soit suffisamment significatif dans la compréhension de ce qu'il représente

Il est aussi recommandé de respecter des normes de développement

 

3.4. Les commentaires

Ils ne sont pas pris en compte par le compilateur donc ils ne sont pas inclus dans le pseudo code. Ils ne se terminent pas par un caractère ";".

Il existe trois types de commentaire en Java :

Type de commentaires Exemple
commentaire abrégé // commentaire sur une seule ligne
int N=1; // déclaration du compteur
commentaire multiligne
/* commentaires ligne 1
   commentaires ligne 2 */
commentaire de documentation automatique
/**
  * commentaire de la méthode
  * @param val la valeur à traiter
  * @since 1.0
  * @return la valeur de retour
  * @deprecated Utiliser la nouvelle méthode XXX
  */

 

3.5. La déclaration et l'utilisation de variables

Une variable, identifié par un nom, permet d'accéder à une valeur. En Java, une variable est obligatoirement typée statiquement à sa définition : une fois la variable définie, son type ne peut pas changer.

 

3.5.1. La déclaration de variables

Une variable possède un nom, un type et une valeur. La déclaration d'une variable doit donc contenir deux choses : un nom et le type de données qu'elle peut contenir. Une variable est utilisable dans le bloc où elle est définie.

La déclaration d'une variable permet de réserver la mémoire pour en stocker la valeur.

Le type d'une variable peut être :

Exemple :
long nombre;
int compteur;
String chaine;

Rappel : les noms de variables en Java peuvent commencer par une lettre, par le caractère de soulignement ou par le signe dollar. Le reste du nom peut comporter des lettres ou des nombres mais jamais d'espace.

Il est possible de définir plusieurs variables de même type en séparant chacune d'elles par une virgule.

Exemple :
int jour, mois, annee ;

Java est un langage à typage rigoureux qui ne possède pas de transtypage automatique lorsque ce transtypage risque de conduire à une perte d'information. A partir de Java 5, l'autoboxing permet la conversion automatique d'un type primitif vers son wrapper correspondant. Le mécanisme inverse est nommé unboxing.

Pour les objets, il est nécessaire en plus de la déclaration de la variable de créer un objet avant de pouvoir l'utiliser. Il faut réserver de la mémoire pour la création d'un objet ( remarque : un tableau est un objet en Java ) avec l'instruction new. La libération de la mémoire se fait automatiquement grâce au garbage collector.

Exemple :
MaClasse instance; // déclaration de l'objet

instance = new MaClasse(); // création de l'objet

OU MaClasse instance = new MaClasse(); // déclaration et création de l'objet

Exemple :
int[] nombre = new int[10];

Il est possible en une seule instruction de faire la déclaration et l'affectation d'une valeur à une variable ou plusieurs variables.

Exemple :
int i=3 , j=4 ;

 

3.5.2. Les types élémentaires

Les types élémentaires ont une taille identique quel que soit la plate-forme d'exécution : c'est un des éléments qui permet à Java d'être indépendant de la plate-forme sur laquelle le code s'exécute.

Type Désignation Longueur Valeurs Commentaires
boolean valeur logique : true ou false
1 bit
true ou false
pas de conversion possible vers un autre type
byte octet signé
8 bits
-128 à 127
 
short entier court signé
16 bits
-32768 à 32767
 
char caractère Unicode
16 bits
\u0000 à \uFFFF
entouré de cotes simples dans du code Java
int entier signé
32 bits
-2147483648 à 2147483647
 
float virgule flottante simple précision (IEEE754)
32 bits
1.401e-045 à 3.40282e+038
 
double virgule flottante double précision (IEEE754)
64 bits
2.22507e-308 à 1.79769e+308
 
long entier long
64 bits
-9223372036854775808 à 9223372036854775807
 

Les types élémentaires commencent tous par une minuscule.

 

3.5.3. Le format des types élémentaires

Le format des nombres entiers :

Il existe plusieurs formats pour les nombres entiers : les types byte, short, int et long peuvent être codés en décimal, hexadécimal ou octal. Pour un nombre hexadécimal, il suffit de préfixer sa valeur par 0x. Pour un nombre octal, le nombre doit commencer par un zéro. Le suffixe l ou L permet de spécifier que c'est un entier long.

Le format des nombres décimaux :

Il existe plusieurs formats pour les nombres décimaux. Les types float et double stockent des nombres flottants : pour être reconnus comme tels ils doivent posséder soit un point, un exposant ou l'un des suffixes f, F, d, D. Il est possible de préciser des nombres qui n'ont pas le partie entière ou pas de partie décimale.

Exemple :
float pi = 3.141f;
double valeur = 3d;
float flottant1 = +.1f , flottant2 = 1e10f;

Par défaut un littéral représentant une valeur décimale est de type double : pour définir un littéral représentant une valeur décimale de type float il faut le suffixer par la lettre f ou F.

stop Attention :
float pi = 3.141; // erreur à la compilation 
float pi = 3.141f; // compilation sans erreur

Exemple :
double valeur = 1.1; 

Le format des caractères :

Un caractère est codé sur 16 bits car il est conforme à la norme Unicode. Il doit être entouré par des apostrophes. Une valeur de type char peut être considérée comme un entier non négatif de 0 à 65535. Cependant la conversion implicite par affectation n'est pas possible.

Exemple :
/* test sur les caractères */
class test1 {
    public static void main (String args[]) {
        char code = 'D';
        int index = code - 'A';
        System.out.println("index = " + index);
    } 
}

 

3.5.4. L'initialisation des variables

Exemple :
int nombre; // déclaration
nombre = 100; //initialisation
OU int nombre = 100; //déclaration et initialisation

En Java, toute variable appartenant à un objet (définie comme étant un attribut de l'objet) est initialisée avec une valeur par défaut en accord avec son type au moment de la création. Cette initialisation ne s'applique pas aux variables locales des méthodes de la classe.

Les valeurs par défaut lors de l'initialisation automatique des variables d'instances sont :

Type
Valeur par défaut
boolean
false
byte, short, int, long
0
float, double
0.0
char
\u000
classe
null

information Remarque : Dans une applet, il est préférable de faire les déclarations et initialisations dans la méthode init().

 

3.5.5. L'affectation

le signe = est l'opérateur d'affectation et s'utilise avec une expression de la forme variable = expression. L'opération d'affectation est associative de droite à gauche : il renvoie la valeur affectée ce qui permet d'écrire :

x = y = z = 0;

Il existe des opérateurs qui permettent de simplifier l'écriture d'une opération d'affectation associée à un opérateur mathématique :

Opérateur
Exemple
Signification
=
a=10
équivalent à : a = 10
+=
a+=10
équivalent à : a = a + 10
-=
a-=10
équivalent à : a = a - 10
*=
a*=10
équivalent à : a = a * 10
/=
a/=10
équivalent à : a = a / 10
%=
a%=10
reste de la division
^=
a^=10
équivalent à : a = a ^ 10
<<=
a<<=10
équivalent à : a = a << 10 a est complété par des zéros à droite
>>=
a>>=10
équivalent à : a = a >> 10 a est complété par des zéros à gauche
>>>=
a>>>=10
équivalent à : a = a >>> 10 décalage à gauche non signé

 

stop Attention : Lors d'une opération sur des opérandes de types différents, le compilateur détermine le type du résultat en prenant le type le plus précis des opérandes. Par exemple, une multiplication d'une variable de type float avec une variable de type double donne un résultat de type double. Lors d'une opération entre un opérande entier et un flottant, le résultat est du type de l'opérande flottant.

 

3.5.6. Les entiers exprimés en binaire (Binary Literals)

Avec Java 7, la valeur des types entiers (byte, short, int, et long) peut être exprimée dans le système binaire en utilisant le préfixe 0b ou 0B

Exemple ( code Java 7 ) :
  public static void testEntierBinaire() {
    byte valeurByte = (byte) 0b00010001;
    System.out.println("valeurByte = " + valeurByte);
    valeurByte = (byte) 0B10001;
    System.out.println("valeurByte = " + valeurByte);
    valeurByte = (byte) 0B11101111;
    System.out.println("valeurByte = " + valeurByte);
    short valeurShort = (short) 0b1001110111101;
    System.out.println("valeurShort = " + valeurShort);
    int valeurInt = 0b1000;
    System.out.println("valeurInt = " + valeurInt);
    valeurInt = 0b1001110100010110100110101000101;
    System.out.println("valeurInt = " + valeurInt);
    long valeurLong =
        0b010000101000101101000010100010110100001010001011010000101000101L;
    System.out.println("valeurLong = " + valeurLong);
  }

 

3.5.7. Utilisation des underscores dans les entiers littéraux

Il n'est pas facile de lire un nombre qui compte de nombreux chiffres : dès que le nombre de chiffres dépasse 9 ou 10 la lecture n'est plus triviale, ce qui peut engendrer des erreurs.

A partir de Java 7, il est possible d'utiliser un ou plusieurs caractères tiret bas (underscore) entre les chiffres qui composent un entier littéral. Ceci permet de faire des groupes de chiffres pour par exemple séparer les milliers, les millions, les milliards, ... afin d'améliorer la lisibilité du code.

Exemple ( code Java 7 ) :
    int maValeur = 123_1456_789;
    maValeur = 4_3;
    maValeur = 4___3;
    maValeur = 0x4_3;     
    maValeur = 0_43;             
    maValeur = 04_3;               
    maValeur = 0b1001110_10001011_01001101_01000101;
    long creditCardNumber = 1234_5678_9012_3456L;
    long numeroSecuriteSociale = 1_75_02_31_235_897L;
    long octetsDebutFichierClass = 0xCAFE_BABE;
    long maxLong = 0x7fff_ffff_ffff_ffffL;
    float pi = 3.141_593f;

Un nombre quelconque de caractères de soulignement (underscore) peut apparaître n'importe où entre les chiffres d'un littéral numérique. Le caractère underscore doit être placé uniquement entre deux chiffres. Il n'est donc pas possible de l'utiliser :

Exemple ( code Java 7 ) :
  // toutes ces expressions provoquent une erreur de compilation
  int maValeur = _43;            
  int maValeur = 43_;             
  int x5 = 0_x43;
  int x6 = 0x_43;           
  int x8 = 0x43_; 
  float pi1 = 3_.141593F;      
  float pi2 = 3._141593F;     
  long numeroSecuriteSociale = 1750231235897_L;

Le caractère underscore ne modifie pas la valeur mais facilite simplement sa lecture.

 

3.6. La déclaration de variables locales avec l'inférence de type

Globalement Java a la réputation d'être assez verbeux, en particulier par rapport à des langages plus récents : c'est une critique commune du langage par les développeurs Java novices et expérimentés.

Java est un langage typé ce qui permet au compilateur de détecter des erreurs liées à une mauvaise utilisation de type mais cela oblige à ajouter du code notamment lors de la définition de variables locales.

Historiquement, Java a toujours exigé que les déclarations de variables locales incluent explicitement le type. Bien que les types explicites puissent être très utiles, parfois ils ne sont pas très importants. Historiquement pour définir une variable et lui affecter une nouvelle instance, il est nécessaire de préciser le type à la déclaration (à gauche de l'opérateur =) et de créer l'instance (à droite de l'opérateur =).

Exemple :
MaClasse mc = new MaClasse();

Cela implique une redondance du type. C'est aussi le cas lors de la déclaration et l'initialisation d'une variable de type primitif ou String : le type doit être explicitement précisé mais il est redondant avec la valeur affectée à la variable.

Jusqu'à Java 9 inclus, la déclaration de toutes variables impose de préciser explicitement le type.

Exemple :
String salutation = "Bonjour";

Sur cette simple déclaration, le fait de préciser explicitement le type est redondant avec la valeur littérale d'initialisation qui implique obligatoire une variable de type String.

Pour une variable locale qui est un objet, cela implique généralement d'utiliser deux fois le type dans le code de déclaration de la variable :

Exemple :
ArrayList<String> liste = new ArrayList<String>();

Si la variable est de type primitif et qu'elle est initialisée, le type utilisé dans la déclaration est redondant avec la valeur.

Exemple :
int valeur = 10;

Java 10 a introduit une nouvelle fonctionnalité dans le langage Java appelée inférence de type pour variable locale via la JEP 286. Le but de la JEP 286 est de permettre au compilateur d'inférer le type des variables locales qui sont initialisées avec une valeur. Le compilateur pourra alors déterminer le type de la variable et dispenser le développeur de l'indiquer explicitement. Elle permet de réduire la quantité de code Java à produire pour un traitement courant : la déclaration de variables locales en introduisant un sucre syntaxique.

L'objectif est de réduire la verbosité de Java, comme le propose déjà de nombreux autres langages modernes typés comme Scala, C#, Go ou Kotlin qui offrent déjà l'inférence de type pour la définition de variables locales et permettent ainsi de déclarer des variables locales sans préciser leur type. Comme dans ces langages, il est possible d'utiliser un instruction, var en Java, pour déclarer une variable locale dont le type est inféré à partir de la valeur qui lui est affectée.

De nombreux langages qui utilisent l'instruction var pour déclarer une variable propose aussi un autre mot clé comme val ou let pour définir une variable immuable. Java 10 ne propose que le mot clé var : il faut donc le compléter avec le mot clé final pour obtenir une déclaration d'une variable dont la valeur est immuable.

Le choix a été fait de ne pas utiliser une instruction val pour définir une variable immuable. L'immutabilité est importante mais son intérêt est très limité pour des variables locales. De plus, Java 8 a introduit la notion de variable effectivement finale.

Cette fonctionnalité va permettre de réduire la quantité de code requise pour déclarer une variable locale en Java depuis sa création. Tant que le compilateur peut déduire le type à partir de la valeur d'initialisation, il n'est plus obligatoire de définir explicitement le type d'une variable locale mais de le remplacer par l'instruction var.

Lors du traitement de var, le compilateur utilise le type de la valeur d'initialisation de la variable pour définir le type de la variable et l'utiliser dans le bytecode.

Lors de la déclaration d'une variable locale initialisée, il est possible d'utiliser var à la place du type ou du type primitif pour indiquer au compilateur d'inférer le type de la variable. Cela n'est possible que pour des variables qui sont initialisée afin de permettre au compilateur de déterminer le type en fonction de la valeur d'initialisation.

Ainsi le type d'une variable locale pourra disparaitre dans le code source mais Java reste un langage typé et c'est simplement le compilateur qui est en charge de déterminer le type et de l'utiliser dans le bytecode.

Cela ne concerne que la déclaration de variables locales :

Cela n'est pas possible dans les autres cas : paramètres de méthodes et constructeurs, type de retour, champs, instruction catch, ...

L'instruction var ne rend pas le langage Java typé dynamiquement : Java reste un langage statiquement typé. Une fois le type inféré par le compilateur, celui-ci est ajouté dans le bytecode et il n'est pas possible d'affecter une valeur non adéquate au type inféré à la variable.

 

3.6.1. L'instruction var

Java 10 propose l'instruction var pour définir une variable locale : l'instruction var remplace le type dans le code. Le compilateur va alors inférer le type de la variable sous réserve que la variable soit initialisée avec une valeur qui permette au compilateur de déterminer le type.

Exemple ( code Java 10 ) :
var salutation = "Bonjour";

Pour les variables locales initialisées explicitement, il est donc possible de remplacer le type de la variable par l'instruction var. Cette nouvelle syntaxe réduit la verbosité associée à la déclaration d'une variable locale en Java, tout en maintenant la sécurité du typage statique : elle permet la déclaration de variables sans avoir à préciser explicitement le type.

Exemple

Code

Type inféré pour la variable

var i = 2 ; int
var i = 2L ; long
var liste = new ArrayList<String>(); ArrayList<String>
var stream = liste.stream(); Stream(<String>)
var chemin = Paths.get("fichier.txt"); Path
var contenu = Files.readAllBytes(chemin); byte[]
var references = new HashMap<Integer, String>(); HashMap<Integer, String>

L'utilisation d'une boucle requiert fréquemment une variable locale : l'instruction var peut aussi être utilisée pour définir une telle variable.

Exemple ( code Java 10 ) :
  public static void main(String[] args) {
    for (var arg : args) {
      System.out.println(arg);
    }
 
    for (var i = 0; i < args.length; i++) {
      var arg = args[i];
      System.out.println(arg);
    }
  }

L'identifiant var n'est pas un mot clé : il n'est pas ajouté à la liste des mots réservés du langage Java. L'identifiant var est un identifiant restreint : il est reconnu comme un mot clé qu'aux endroits où il peut être interprété comme tel dans le contexte de la déclaration d'une variable locale. Ainsi même si ce n'est pas recommandé, il est possible de déclarer une variable locale nommée var.

Exemple ( code Java 10 ) :
var var = 2;

Ainsi la majorité du code existant qui utilise var comme identifiant se compile correctement en Java 10. Comme var n'est pas un mot clé, il est toujours possible de nommer une variable, un champ, un paramètre, une méthode ou un package avec var.

Par contre, un type nommé var va poser des soucis : est-ce que var est l'instruction ou le type ? Ce cas devrait être rare puisque la convention de nommage très largement utilisée préconise de commencer le nom d'un type par une majuscule.

Il n'est pas possible d'utiliser var comme nom d'un type (une classe, une interface, une énumération, un record) : cette limitation est ajoutée dans les spécifications du langage Java.

 

3.6.2. Les restrictions d'utilisation de l'instruction var

L'utilisation de l'instruction var doit respecter plusieurs contraintes qui seront vérifiées lors de la compilation.

L'utilisation de l'identifiant var pour inférer le type d'une variable locale n'est utilisable que dans certaines circonstances. Il n'est pas possible d'utiliser l'inférence de type sur la déclaration de variables locales :

L'instruction var n'est utilisable que pour définir des variables locales : elle ne peut donc être utilisée que dans le corps d'une méthode ou d'un bloc de code ou d'une expression Lambda.

Exemple ( code Java 10 ) :
Consumer<String> c = s -> {var i = 0; System.out.println(i); };

Il n'est pas possible d'utiliser l'instruction var dans un autre contexte : il n'est donc par exemple pas possible de l'utiliser avec des paramètres de méthodes, des valeurs de retour de méthodes, des champs ...

L'instruction var n'est utilisable que pour définir une variable locale : dans les autres cas, il est toujours obligatoire de préciser explicitement le type.

Résultat :
jshell> private var getValeur() { return 10; }
|  Error:
|  'var' is not allowed here
|  private var getValeur() { return 10; }
|          ^-^

Il n'est pas possible d'utiliser l'instruction var pour indiquer le type de retour d'une méthode.

Exemple ( code Java 10 ) :
  var getLibelle() {       // erreur de compilation
    return "Description"; 
  }

Il n'est pas possible d'utiliser var pour définir un paramètre d'une méthode.

Exemple ( code Java 10 ) :
  void saluer(var nom) {     // erreur de compilation
    System.out.println("Bonjour "+nom);
  }

Le choix de limiter l'utilisation de l'instruction var uniquement à la déclaration de variables locales est dictée pour maintenir l'inférence à côté de la déclaration et ainsi limiter les erreurs pouvant être induites plus loin dans le code.

Le compilateur doit être en mesure d'inférer le type : cela impose que la variable soit initialisée avec une valeur qui permettre au compilateur de déterminer le type inféré.

Résultat :
jshell> var valeur;
|  Error:
|  cannot infer type for local variable valeur
|    (cannot use 'var' on variable without initializer)
|  var valeur;
|  ^---------^

La variable doit être initialisée à sa déclaration pour permettre au compilateur d'inférer le type par rapport à la valeur assignée.

Exemple ( code Java 10 ) :
    var salutation;            // erreur de compilation
    salutation = "Bonjour";

La compilation du code ci-dessus provoque une erreur « cannot infer type for local variable salutation ».

Il n'est pas possible d'utiliser l'instruction var avec une variable initialisée avec la valeur null.

Résultat :
jshell> var titres = null;
|  Error:
|  cannot infer type for local variable titres
|    (variable initializer is 'null')
|  var titres = null;
|  ^----------------^

Il n'est pas possible d'utiliser l'instruction var pour déclarer plusieurs variables en même temps, même si le type inféré par le compilateur est le même pour toutes les variables.

Résultat :
jshell> var x, y = 0;
|  Error:
|  'var' is not allowed in a compound declaration
|  var x, y = 0;
|      ^
 
jshell> var x = 0, y = 0;
|  Error:
|  'var' is not allowed in a compound declaration
|  var x = 0, y = 0;
|      ^

Il faut utiliser une instruction var dédiée pour chaque variable.

Résultat :
jshell> var x=0; var y=0;
x ==> 0
y ==> 0

Il n'est pas possible d'utiliser l'instruction var pour une variable qui n'a pas encore de type cible dû au fait qu'il n'est pas encore inféré (poly expressions) tels que les expressions Lambda, les références de méthodes ou l'initialisation d'un tableau avec des valeurs.

Il n'est pas possible d'utiliser l'instruction var avec une initialisation de tableau.

Résultat :
jshell> var chaines = {"e1","e2"};
|  Error:
|  cannot infer type for local variable chaines
|    (array initializer needs an explicit target-type)
|  var chaines = {"e1","e2"};
|  ^------------------------^

Il faut dans ce cas obligatoirement invoquer le constructeur pour créer une instance du tableau qui sera initialisée avec les valeurs fournies.

Résultat :
jshell> var chaines = new String[] {"e1","e2"}
chaines ==> String[2] { "e1", "e2" }
|  created variable chaines : String[]

Ainsi, il n'est pas possible d'initialiser une variable déclarée avec var et initialisée avec une expression lambda ou une référence de méthode.

Exemple ( code Java 10 ) :
    var salutation = () -> "Bonjour";    // erreur de compilation

Il n'est pas possible d'utiliser l'instruction var avec une expression Lambda comme valeur sans préciser explicitement l'interface fonctionnelle

Résultat :
jshell> var affichage = () -> { System.out.println("Bonjour"); };
|  Error:
|  cannot infer type for local variable affichage
|    (lambda expression needs an explicit target-type)
|  var affichage = () -> { System.out.println("Bonjour"); };
|  ^-------------------------------------------------------^

Il n'est pas possible d'utiliser l'instruction var avec une référence de méthode.

Résultat :
jshell>  var max = Math::max;
|  Error:
|  cannot infer type for local variable max
|    (method reference needs an explicit target-type)
|   var max = Math::max;
|   ^------------------^
 
jshell> var comparerStr = String::compareTo
|  Error:
|  cannot infer type for local variable comparerStr
|    (method reference needs an explicit target-type)
|  var comparerStr = String::compareTo;
|  ^----------------------------------^

Il faut obligatoirement caster l'expression lambda ou la référence de méthode avec l'interface fonctionnelle correspondante.

Résultat :
jshell> var additionner = (IntBinaryOperator) (a,b) -> a+b
additionner ==> $Lambda$21/762227630@4e7dc304
|  created variable additionner : IntBinaryOperator

Il est possible de modifier la valeur d'une variable définie avec var tant que la valeur est affectable au type inféré.

Exemple ( code Java 10 ) :
    var salutation = "Bonjour" ;
    salutation = "Hello";

Java reste statiquement typé : une fois le type inféré par le compilateur, il est utilisé dans le byte code. Il n'est pas possible de modifier le type d'une variable ou de lui affecter une valeur qui ne corresponde pas à son type

Exemple ( code Java 10 ) :
    var valeur = 0 ;
    valeur = "test";       // erreur de compilation

 

3.6.3. Les avantages et les inconvénients

L'inférence de type pour les variables locales avec l'instruction var est une fonctionnalité qui fait l'objet de controverses. Certains apprécient la concision de la déclaration des variables locales : le code est plus court grâce à l'élimination du type qui est généralement redondant. D'autres craignent que l'absence du type nuise à la lisibilité due à l'élimination du type.

Comme pour toutes les fonctionnalités, il doit être utilisé avec discernement. Il n'y a pas de règles absolues pour savoir quand elle devrait et ne devrait pas être utilisée.

La règle essentielle est que la lecture du code est plus importante que l'écriture du code.

 

3.6.3.1. La facilitation de la déclaration de variables locales

L'utilisation de l'instruction var est une fonctionnalité du langage Java dont le but est de faciliter la déclaration et l'initialisation de variables locales en laissant le compilateur inférer le type. C'est un sucre syntaxique qui délègue au compilateur la responsabilité de déterminer le type lors de la définition d'une variable locale en fonction de sa valeur d'initialisation. L'inférence de type pour les variables locales permet de réduire un peu la quantité de code nécessaire à la déclaration de variables locales.

L'instruction var réduit la quantité de code requise pour déclarer des variables locales en Java. C'est notamment le cas lorsque le type de la variable est long voir très long car il inclut par exemple plusieurs types génériques.

L'instruction var facilite la déclaration de variables intermédiaires et ainsi de permettre leur accès. Il est aussi fréquent de définir une variable qui est uniquement utilisé sur la ligne suivante pour définir une autre variable.

Exemple :
    URL url = new URL("http://www.google.com");
    URLConnection con = url.openConnection();
    Reader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
    reader.close();

L'utilisation de l'inférence pour les variables locales réduit la quantité de code à écrire pour obtenir le même résultat.

Exemple :
    var url = new URL("http://www.google.com")
    var con = url.openConnection();
    var reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
    reader.close();

Cela réduit la quantité de code en évitant la redondance sur le type mais cela améliore aussi la lisibilité puisque toutes les variables sont alignées sur la même colonne.

Avec l'instruction var, il est beaucoup plus facile de déclarer des variables intermédiaires voire même de le faire à des endroits où la complexité nous aurait rebuté de le faire auparavant. C'est notamment le cas lors de l'utilisation d'expressions imbriquées ou chaînées.

 

3.6.3.2. La lisibilité

Comme avec d'autres langages qui proposent cette fonctionnalité, l'inférence du type de variables locales peut conduire à du code plus ou moins lisible : il est de la responsabilité du développeur d'utiliser cette fonctionnalité avec discernement.

L'omission d'un type explicite peut réduire le code nécessaire, mais seulement si son omission ne nuit pas à la compréhension. Le type n'est pas le seul moyen de fournir de l'information : il est aussi possible d'en fournir via le nom de la variable et la valeur d'initialisation.

L'instruction var permet de réduire la quantité de code lors de la déclaration d'une variable locale, mais sans y prêter attention son utilisation peut aussi en réduire la lisibilité et la compréhension.

Exemple ( code Java 10 ) :
    var resultats = rechercherService.obtenirPremiers();

La lecture du code ci-dessus ne permet pas facilement de connaître le type de la variable inféré par le compilateur. Evidemment, les IDE fournissent des fonctionnalités pour afficher le type de la variable mais le code n'est pas toujours lu dans un IDE, par exemple comme dans une page sur le web ou dans des outils de revue de code.

C'est aussi vrai lors du remplacement d'un type explicite par l'utilisation d'une instruction var.

Exemple ( code Java 8 ) :
    List<Resultat> r = rechercherService.obtenirPremiers();

Exemple ( code Java 10 ) :
    var r = rechercherService.obtenirPremiers();

L'utilisation de noms de variable courts n'est généralement pas une bonne pratique mais dans ce cas, il est encore plus recommandé d'accorder une importance accrue dans le nommage des variables déclarées avec l'instruction var.

Dans certains cas, l'utilisation de l'instruction var ne permet que de réduire la quantité de code à produire. Cependant, elle peut permettre de rendre le code plus lisible notamment si le type de la variable est très long (par ce que son nom est très long et il utilise un ou plusieurs types génériques qui peuvent être imbriqués)

Exemple ( code Java 10 ) :
Commande<Client, Produits<Map<Reference, Integer>>, TypeCommande> commande = 
    traiterCommandeUrgente(client, articles);

Le fait de ne plus voire explicitement le type peut parfois être handicapant : bien sûr les IDE permettent facilement de connaitre le type de la variable mais sans IDE cela peut être plus difficile comme par exemple lors d'une revue de code ou de la lecture de code sur Internet.

Généralement l'absence de type réduit la compréhension du code notamment car la lecture du type facilite la compréhension du code notamment lors de l'utilisation de type ou de type complexe avec générique. Les IDE proposent des fonctionnalités pour afficher le type d'un élément du code source mais cela requiert d'avoir un IDE.

Par contre l'utilisation de l'instruction var améliore la lisibilité car les noms des variables déclarées peuvent être alignées, même si là aussi les IDE peuvent réaliser cet alignement.

Le type est important mais le nom d'une variable doit l'être aussi : il l'est d'autant plus avec l'utilisation de l'instruction var car le type n'est plus visible dans le code source.

Il est de la responsabilité des développeurs d'assurer un équilibre entre verbosité et clarté du code. Sans type visible et avec des noms de variables peu compréhensifs, le code peut devenir moins facile à comprendre.

Exemple ( code Java 10 ) :
    // Personne p = service.get();
    var p = service.get();

Il est donc important de n'utiliser l'instruction var que lorsque cela rend le codeplus concis mais pas moins clair : elle ne devrait pas forcement être utilisé systématiquement. D'autant que l'utilisation de var peut encourager l'utilisation de variables locales et donc l'écriture de méthodes plus longues.

 

3.6.4. Quelques mises en garde sur l'utilisation de var

Le code Java reste statiquement typé mais avec l'utilisation de l'identifiant var, le développeur ne voit plus le type. C'est notamment le cas si la valeur de la variable locale est initialisée avec la valeur de retour d'une méthode.

Exemple ( code Java 10 ) :
jshell> public int getValeur() { return 10; }
|  created method getValeur()
 
jshell> var valeur = getValeur()
valeur ==> 10
|  created variable valeur : int

Le compilateur infère le type le plus proche possible : le type déterminé lors de l'inférence n'est pas un super type.

L'exemple ci-dessous ne fonctionne pas car le type inféré n'est pas List<String> mais ArrayList<String>. Il n'est donc pas possible d'affecter une instance de type LinkedList<String> à la variable.

Résultat :
jshell> var liste = new ArrayList<String>();
liste ==> []
 
jshell> liste = new LinkedList<String>();
|  Error:
|  incompatible types: java.util.LinkedList<java.lang.String> cannot be converted 
to java.util.ArrayList<java.lang.String>
|  liste = new LinkedList<String>();
|          ^----------------------^

Pour que cet exemple puisse fonctionner, il faut obligatoirement préciser explicitement le type de la variable et ne pas laisser le compilateur le déterminer par inférence.

Le type inféré est parfois non trivial.

Exemple ( code Java 10 ) :
jshell> var liste = List.of(1.0, 1, "un")
liste ==> [1.0, 1, un]
|  created variable liste : List<Serializable&Comparable<? extends Serializable&Comparable<?>>>

Le type inféré par le compilateur est le résultat de l'intersection des types des valeurs fournies dans la collection. La complexité de l'intersection des types peut s'accroitre avec la version de Java utilisée.

Exemple ( code Java 10 ) :
jshell> var liste = List.of(1, "un")
liste ==> [1, un]
|  created variable liste : List<Serializable&Comparable<? extends Serializable&Comparable<?>>>

Exemple ( code Java 12 ) :
jshell> var liste = List.of(1, "un")
liste ==> [1, un]
|  created variable liste : List<Serializable&Comparable<? extends Serializable&Comparable<?>
&java.lang.constant.Constable&java.lang.constant.ConstantDesc>&java.lang.constant.Constable
&java.lang.constant.ConstantDesc>

Avant Java 10, le type d'une classe anonyme interne ne peut pas être déterminé pour être utilisé dans le code.

Dans l'exemple ci-dessous, la classe n'implémente pas un super type qui contienne la méthode à invoquer. Il est possible d'utiliser le type Object mais il ne sera pas possible d'invoquer la méthode afficher().

Exemple (code Java 1.1) :
(new Object() {
  void afficher() {
    System.out.println("test");
  }
}).afficher();

Avec cette syntaxe, le résultat de l'invocation du constructeur est chaîné à l'invocation de la méthode. Cette syntaxe n'est utilisable que si la classe ne possède qu'une seule méthode à invoquer.

Avec l'instruction var, il est possible de déclarer une variable dont le type est la classe anonyme et d'obtenir le type de la classe. La variable ayant le type réel, il est possible d'invoquer la méthode afficher().

En utilisant l'inférence de type avec l'instruction var, il est possible de définir une variable dont la valeur est une instance d'une classe anonyme interne.

Exemple ( code Java 10 ) :
jshell> var objet = new Object() {
   ...> void afficher() {
   ...> System.out.println("test");
   ...> }
   ...> }
objet ==> $1@3fee9989
|  created variable objet : <anonymous class extending Object>
 
jshell> objet.afficher();
test

Comme la variable contient une référence sur l'instance du type de la classe interne, il est possible d'invoquer d'autres méthodes de l'instance.

Exemple ( code Java 10 ) :
jshell> var objet = new Object() {
   ...> void afficher() {
   ...> System.out.println("afficher");
   ...> }
   ...> void calculer() {
   ...> System.out.println("calculer");
   ...> }
   ...> }
objet ==> $1@7085bdee
 
jshell> objet.afficher();
afficher
 
jshell> objet.calculer();
calculer

Lors de l'utilisation de l'instruction avec une variable initialisée avec l'invocation d'un constructeur d'une classe générique avec l'opérateur diamant, le type inféré sera paramétré avec Object.

Exemple ( code Java 10 ) :
jshell> var references = new ArrayList<>();
references ==> []
|  created variable references : ArrayList<Object>

Cette situation survient notamment lors du remplacement du type explicite par une instruction var.

Exemple ( code Java 10 ) :
jshell> List<String> references = new ArrayList<>();
references ==> []
|  created variable references : List<String>

Pour imposer le type paramétré inféré, il faut préciser l'argument de type lors de l'invocation du constructeur.

Exemple ( code Java 10 ) :
jshell> var references = new ArrayList<String>();
references ==> []
|  created variable references : ArrayList<String>

Dans les exemples ci-dessus, il est important de noter que le type inféré n'est pas List<> mais ArrayList<>.

 

3.6.5. Les messages d'erreur courants lors d'une utilisation incorrecte de var

Différents messages d'erreur sont émis par le compilateur javac du JDK en cas de situation illicite dans l'utilisation de l'instruction var.

Lorsque la variable n'est pas initialisée :

Exemple ( code Java 10 ) :
public class TestVar {
 
  public static void main(String[] args) {
    var valeur;
  }
}

Résultat :
C:\java>javac TestVar.java
TestVar.java:4: error: cannot infer type for local variable valeur
    var valeur;
        ^
  (cannot use 'var' on variable without initializer)
1 error

Lorsque la variable est initialisée avec null :

Exemple ( code Java 10 ) :
public class TestVar {
 
  public static void main(String[] args) {
    var obj = null;
  }
}

Résultat :
C:\java>javac TestVar.java
TestVar.java:4: error: cannot infer type for local variable obj
    var obj = null;
        ^
  (variable initializer is 'null')
1 error

Lorsque plusieurs variables sont définies avec la même instruction var :

Exemple ( code Java 10 ) :
public class TestVar {
 
  public static void main(String[] args) {
    var a = 1, b = 2;
  }
}

Résultat :
C:\java>javac TestVar.java
TestVar.java:4: error: 'var' is not allowed in a compound declaration
    var a = 1, b = 2;
        ^
1 error

Lorsque la variable est initialisée avec une expression Lambda sans la caster avec le type de son interface fonctionnelle correspondante :

Exemple ( code Java 10 ) :
public class TestVar {
 
  public static void main(String[] args) {
    var additionner = (a,b) -> a + b;
  }
}

Résultat :
C:\java>javac TestVar.java
TestVar.java:4: error: cannot infer type for local variable additionner
    var additionner = (a,b) -> a + b;
        ^
  (lambda expression needs an explicit target-type)
1 error

Lorsque la variable est initialisée avec une référence de méthode sans la caster avec le type de son interface fonctionnelle correspondante :

Exemple ( code Java 10 ) :
public class TestVar {
 
  public static void main(String[] args) {
    var afficher = System.out::println;
  }
}

Résultat :
C:\java>javac TestVar.java
TestVar.java:4: error: cannot infer type for local variable afficher
    var afficher = System.out::println;
        ^
  (method reference needs an explicit target-type)
1 error

Lorsque la variable est initialisée avec l'initialisation d'un tableau sans en préciser explicitement le type :

Exemple ( code Java 10 ) :
public class TestVar {
 
  public static void main(String[] args) {
    var valeurs = {1, 2, 3};
  }
}

Résultat :
C:\java>javac TestVar.java
TestVar.java:4: error: cannot infer type for local variable valeurs
    var valeurs = {1, 2, 3};
        ^
  (array initializer needs an explicit target-type)
1 error

 

3.6.6. Les comparaisons

Java propose des opérateurs pour toutes les comparaisons :

Opérateur Exemple Signification
>
a > 10
strictement supérieur
<
a < 10
strictement inférieur
>=
a >= 10
supérieur ou égal
<=
a <= 10
inférieur ou égal
==
a == 10
Egalité
!=
a != 10
diffèrent de
&
a & b
ET binaire
^
a ^ b
OU exclusif binaire
|
a | b
OU binaire
&&
a && b
ET logique (pour expressions booléennes) : l'évaluation de l'expression cesse dès qu'elle devient fausse
||
a || b
OU logique (pour expressions booléennes) : l'évaluation de l'expression cesse dès qu'elle devient vraie
? :
a ? b : c
opérateur conditionnel : renvoie la valeur b ou c selon l'évaluation de l'expression a (si a alors b sinon c) : b et c doivent retourner le même type

Les opérateurs sont exécutés dans l'ordre suivant à l'intérieur d'une expression qui est analysée de gauche à droite:

L'usage des parenthèses permet de modifier cet ordre de priorité.

 

3.7. Les opérations arithmétiques

Les opérateurs arithmétiques se notent + (addition), - (soustraction), * (multiplication), / (division) et % (reste de la division). Ils peuvent se combiner à l'opérateur d'affectation

Exemple :
nombre += 10;

 

3.7.1. L'arithmétique entière

Pour les types numériques entiers, Java met en oeuvre une sorte de mécanisme de conversion implicite vers le type int appelé promotion entière. Ce mécanisme fait partie des règles mises en place pour renforcer la sécurité du code.

Exemple :
short x= 5 , y = 15;
x = x + y ; //erreur à la compilation

Incompatible type for =. Explicit cast needed to convert int to short.
x = x + y ; //erreur à la compilation
^
1 error

Les opérandes et le résultat de l'opération sont convertis en type int. Le résultat est affecté dans un type short : il y a donc risque de perte d'informations et donc une erreur est émise à la compilation. Cette promotion évite un débordement de capacité sans que le programmeur soit pleinement conscient du risque : il est nécessaire, pour régler le problème, d'utiliser une conversion explicite ou cast.

Exemple :
x = (short) ( x + y );

Il est nécessaire de mettre l'opération entre parenthèses pour que ce soit son résultat qui soit converti car le cast a une priorité plus forte que les opérateurs arithmétiques.

La division par zéro pour les types entiers lève l'exception ArithmeticException.

Exemple :
/* test sur la division par zéro de nombres entiers */ 
class test3 {
  public static void main (String args[]) {
    int valeur=10;
    double resultat = valeur / 0;
    System.out.println("index = " + resultat);
  }
}

 

3.7.2. L'arithmétique en virgule flottante

Avec des valeurs float ou double, la division par zéro ne produit pas d'exception mais le résultat est indiqué par une valeur spéciale qui peut prendre trois états :

Conformément à la norme IEEE754, ces valeurs spéciales représentent le résultat d'une expression invalide NaN, une valeur supérieure au plafond du type pour infini positif ou négatif.

X
Y
X / Y
X % Y
valeur finie
0
+ ∞
NaN
valeur finie
+/- ∞
0
x
0
0
NaN
NaN
+/- ∞
valeur finie
+/- ∞
NaN
+/- ∞
+/- ∞
NaN
NaN

 

Exemple :
/* test sur la division par zéro de nombres flottants */ 

class test2 {
  public static void main (String args[]) {
    float valeur=10f; 
    double resultat = valeur / 0;
    System.out.println("index = " + resultat);
  }
}

 

3.7.3. L'incrémentation et la décrémentation

Les opérateurs d'incrémentation et de décrémentation sont : n++ ++n n-- --n

Si l'opérateur est placé avant la variable (préfixé), la modification de la valeur est immédiate sinon la modification n'a lieu qu'à l'issue de l'exécution de la ligne d'instruction (postfixé)

L'opérateur ++ renvoie la valeur avant incrémentation s'il est postfixé, après incrémentation s'il est préfixé.

Exemple :
System.out.println(x++);
// est équivalent à
System.out.println(x); x = x + 1;

System.out.println(++x);
// est équivalent à
x = x + 1; System.out.println(x);

 

Exemple :
/* Test sur les incrémentations préfixées et postfixées */

class test4 {
  public static void main (String args[]) {
    int n1=0;
    int n2=0;
    System.out.println("n1 = " + n1 + " n2 = " + n2);
    n1=n2++;         
    System.out.println("n1 = " + n1 + " n2 = " + n2);
    n1=++n2;        
    System.out.println("n1 = " + n1 + " n2 = " + n2);
    n1=n1++;        //attention
    System.out.println("n1 = " + n1 + " n2 = " + n2);
  }
}

 

Résultat :
int n1=0;
int n2=0; // n1=0 n2=0
n1=n2++; // n1=0 n2=1
n1=++n2; // n1=2 n2=2
n1=n1++; // attention : n1 ne change pas de valeur

 

3.8. La priorité des opérateurs

Java définit les priorités dans les opérateurs comme suit ( du plus prioritaire au moins prioritaire )

les opérateurs postfix
expr++
expr--
les opérateurs unaires
++expr
--expr
+expr
-expr
˜
!
les opérateurs de multiplication, division et modulo
*
/
%
les opérateurs d'addition et soustraction
+
-
les opérateurs de décalage
<<
>>
>>>
les opérateurs de comparaison
<
>
<=
>=
instanceof
les opérateurs d'égalité
==
!=
l'opérateur OU exclusif
^
l'opérateur ET
&
l'opérateur OU
|
l'opérateur ET logique
&&
l'opérateur OU logique
||
l'opérateur ternaire
? :
les opérateurs d'assignement
=
+=
-=
*=
/=
%=
^=
|=
<<=
>>=
>>>=
l'opérateur arrow
->

L'ordre d'interprétation des opérateurs peut être modifié par l'utilisation de parenthèses.

 

3.9. Les structures de contrôles

Comme la quasi-totalité des langages de développement orientés objets, Java propose un ensemble d'instructions qui permettent d'organiser et de structurer les traitements. L'usage de ces instructions est similaire à celui rencontré avec leur équivalent dans d'autres langages.

 

3.9.1. Les boucles

Les boucles permettent d'exécuter plusieurs fois un bloc de code selon une condition ou le parcours des éléments d'un tableau ou d'un Iterable. Java propose plusieurs types de boucles :

 

3.9.1.1. Les boucles while

Une boucle while permet d'exécuter l'instruction ou le bloc de code qui la suit tant que la condition booléenne est évaluée à vraie. La condition est évaluée en début de chaque itération. La syntaxe générale est de la forme :

while ( boolean ) {
  // code à exécuter dans la boucle
}
Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    int i = 1;
    while (i <= 3) {
      System.out.println(i++);
    }
  }
}

Résultat :
1
2
3

Si avant l'instruction while, le booléen est false, alors le code de la boucle ne sera jamais exécuté.

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    System.out.println("debut");
    int i = 4;
    while (i <= 3) {
      System.out.println(i++);
    }
    System.out.println("fin");
  }
}

Résultat :
debut
fin

Attention : ne pas mettre de ; après les parenthèses de la condition sinon cela produira une boucle infinie qui consomme énormément de CPU uniquement pour tester la condition.

 

3.9.1.2. Les boucles do ... while

Une boucle do ... while permet d'exécuter l'instruction ou le bloc de code qui la suit tant que la condition booléenne est évaluée à vraie.

La syntaxe générale est de la forme :

do {
  // ...
} while ( boolean );
Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    int i = 1;
    do {
      System.out.println(i++);
    } while (i <= 3);
  }
}

Résultat :
1
2
3

La condition est évaluée en fin de chaque itération : la boucle est donc exécutée au moins une fois quelle que soit la valeur du booléen lors de la première itération.

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    int i = 4;
    do {
      System.out.println(i++);
    } while (i <= 3);
  }
}

Résultat :
4

 

3.9.1.3. Les boucles for

La boucle for permet de réaliser une boucle dont la définition est composée de plusieurs parties optionnelles. La syntaxe générale est :

for ( initialisation; condition; modification ) {
    ...
}
Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    int i = 0;
    for (i = 1; i < 4; i++) {
      System.out.println(i);
    }
  }
}

Attention : comme toutes les parties sont optionnelles, il est facile de faire une boucle infinie qui va consommer 100% de CPU jusqu'à ce que la JVM soit tuée.

Exemple :
for ( ; ; ) {
} // boucle infinie

L'initialisation, la condition et la modification de l'index sont optionnelles.

Dans l'initialisation, on peut déclarer une variable qui servira d'index. La variable incrémentée peut être définie dans la partie initialisation et qui sera dans ce cas locale à la boucle : elle ne sera donc accessible que dans le bloc de code de l'instruction for.

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    for (int i = 1; i < 4; i++) {
      System.out.println(i);
    }
  }
}

Il est possible d'inclure plusieurs traitements dans l'initialisation et la modification de la boucle : chacun des traitements doit être séparé par une virgule.

Exemple :
  for (i = 0 , j = 0 ; i * j < 1000; i++ , j+= 2) { 
    // ...
  }

La condition peut ne pas porter sur l'index de la boucle :

Exemple :
  boolean trouve = false;
  for (int i = 0 ; !trouve ; i++ ) { 
    if ( tableau[i] == 1 ) {
      trouve = true;
      // gestion de la fin du parcours du tableau
      // ...
    }
  }

 

3.9.1.4. Les boucles for améliorées

Java 1.5 propose une nouvelle syntaxe pour les boucles for : les boucles for améliorées pour faciliter le parcours intégral des implémentations de l'interface Iterator et des tableaux.

L'itération sur les éléments d'une collection est verbeuse avec la déclaration d'un objet de type Iterator.

Exemple ( code Java 5.0 ) :
import java.util.*;
 
public class BoucleForAvecIterator {
 
  public static void main(String[] args) {
    List liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(i);
    }
    
    for (Iterator iter = liste.iterator(); iter.hasNext(); ) {
      System.out.println(iter.next());
    }
  }
}

La nouvelle forme de l'instruction for, spécifiée dans la JSR 201, permet de simplifier l'écriture du code pour réaliser une telle itération et laisse le soin au compilateur de générer le bytecode nécessaire.

Exemple ( code Java 5.0 ) :
import java.util.*;
 
public class TestFor {
 
  public static void main(String[] args) {
    List liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(i);
    }
    
    for (Object element : liste) {
      System.out.println(element);
    }
  }
}

L'utilisation de la syntaxe de l'instruction for améliorée peut être renforcée en combinaison avec les génériques, ce qui évite l'utilisation d'un cast.

Exemple ( code Java 5.0 ) :
import java.util.*;
import java.text.*;
 
public class BoucleForAvecGeneriques {
 
  public static void main(String[] args) {
    List<Date> liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(new Date());
    }
    
    DateFormat df = DateFormat.getDateInstance();
 
    for (Date element : liste) {
      System.out.println(df.format(element));
    }
  }
}

La syntaxe de l'instruction for améliorée peut aussi être utilisée pour parcourir tous les éléments d'un tableau.

Exemple ( code Java 5.0 ) :
import java.util.*;
 
public class BoucleForAvecTableau {
 
  public static void main(String[] args) {
    int[] tableau = {0,1,2,3,4,5,6,7,8,9};
    
    for (int element : tableau) {
      System.out.println(element);
    }
  }
}

Cela permet d'éviter la déclaration et la gestion dans le code d'une variable contenant l'index courant lors du parcours du tableau.

 

3.9.2. Les branchements conditionnels

Les instructions de branchements conditionnels permettent l'exécution d'un bloc de code selon le résultat de l'évaluation d'une condition.

 

3.9.2.1. L'instruction if

L'instruction if permet d'exécuter l'instruction ou le bloc de code qui la suit si l'évaluation de la condition booléenne est vraie.

Il est possible d'utiliser une instruction optionnelle else qui précise une instruction ou un bloc de code à exécuter si l'évaluation de la condition booléenne est fausse. La syntaxe générale est de la forme :

if (boolean) {
    ...
} else if (boolean) {
    ...
} else {
    ...
}
Exemple :
  public static void main(String[] args) {
    estPaire(2);
    estPaire(3);
  }
 
  public static void estPaire(int val) {
    if (val % 2 == 0) {
      System.out.println(val + " est paire");
    } else {
      System.out.println(val + " est impaire");
    }
  }

Résultat :
2 est paire
3 est impaire

 

3.9.2.2. L'opérateur ternaire

L'opérateur ternaire est un raccourci syntaxique pour une instruction if/else qui renvoie simplement une valeur selon l'évaluation de la condition.

La syntaxe générale est de la forme :

( condition ) ? valeur-vraie : valeur-fausse
Exemple :
    if (niveau == 5) // équivalent à total = (niveau == 5) ? 10 : 5;
      total = 10;
    else total = 5;

Il est possible d'imbriquer des opérateurs ternaires mais ce n'est pas recommandé car cela nuit à la lisibilité du code.

 

3.9.2.3. L'instruction switch

L'instruction switch permet d'exécuter du code selon l'évaluation de la valeur d'une expression. La syntaxe historique générale est de la forme :

switch (expression) {
  case valeur1 :
    instr11;
    instr12;
    break;
  case valeur2 :
  case valeur3 :
    ...
    break;
  case valeur4 :
    ...
    break;
  default :
    ...
}

Si une instruction case ne contient pas de break alors les traitements associés au case suivant sont exécutés : cette fonctionnalité se nomme le fall through. Cela permet d'exécuter les mêmes instructions pour plusieurs valeurs.

Avant Java 7, on ne peut utiliser l'instruction switch qu'avec des types primitifs d'une taille maximum de 32 bits (byte, short, int, char) ou une énumération. L'utilisation d'une chaîne de caractères dans une instruction switch provoquait une erreur à la compilation "Incompatible Types. Require int instead of String".

A partir de Java SE 7, il est possible d'utiliser un objet de type String dans l'expression fournie à l'instruction switch.

Exemple ( code Java 7 ) :
  public static Boolean getReponse(String reponse) {
    Boolean resultat = null;
    switch(reponse) {
      case "oui" : 
      case "Oui" :
        resultat = true;
        break;
      case "non" : 
      case "Non" :
        resultat = false;
        break;
      default: 
        resultat = null;
        break;
    }
    return resultat;
  }

L'instruction switch compare la valeur de la chaîne de caractères avec la valeur fournie à chaque instruction case comme si elle utilisait la méthode String.equals(). Dans les faits, le compilateur utilise la méthode String.hashCode() pour faire la comparaison. Le compilateur va ainsi générer un code qui est plus optimisé que le code équivalent avec des instructions if/else.

Important : il est nécessaire de vérifier que la chaîne de caractères évaluée par l'instruction switch ne soit pas null sinon une exception de type NullPointerException est levée.

Le test réalisé par l'instruction switch est sensible à la casse : il faut donc en tenir compte si un test ne l'est pas.

Exemple ( code Java 7 ) :
    public static Boolean getReponse(String reponse) {
    Boolean resultat = null;
    
    switch (reponse.toLowerCase()) {
      case "oui":
        resultat = true;
        break;
      case "non":
        resultat = false;
        break;
      default:
        resultat = null;
        break;
    }
    return resultat;
  }

A partir de Java 14, l'instruction switch a été revue en profondeur pour proposer 4 formes syntaxiques, une utilisation comme instruction ou comme une expression, la possibilité de gérer plusieurs valeurs dans un case. Ces nouvelles possibilités sont détaillées dans une section suivante.

Il est possible d'imbriquer des instructions switch même si cela n'est pas recommandé pour des raisons de lisibilité.

Pour certains cas d'usage, l'instruction switch peut être remplacée avantageusement par une utilisation du polymorphisme.

 

3.9.3. Les débranchements

Le langage Java propose plusieurs instructions pour réaliser des débranchements :

Les deux premières instructions peuvent être utilisées avec une étiquette.

Remarque : l'instruction goto est un mot clé réservé du langage Java mais non utilisé.

Attention : l'utilisation des débranchements est à utiliser avec parcimonie car elle peut contribuer à dégrader la lecture et la maintenabilité du code et induire des bugs subtils.

 

3.9.3.1. L'instruction break

L'instruction break permet de quitter immédiatement une boucle ou un branchement. Elle est utilisable dans tous les contrôles de flot.

La syntaxe est de la forme

break;

L'instruction break est utilisée pour mettre fin à une boucle dès qu'une instruction Break est exécutée dans une boucle, l'itération courante de la boucle s'arrête immédiatement et le contrôle revient à l'instruction suivant la boucle.

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    for (int i = 1; i < 5; i++) {
      if (i == 3) break;
      System.out.println(i);
    }
  }
}

Résultat :
1
2

Remarque : l'instruction break, lorsqu'elle est utilisée à l'intérieur d'un ensemble de boucles imbriquées, n'interrompt que la boucle la plus interne.

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    for (int i = 1; i < 3; i++) {
      for (int j = 1; j < 5; j++) {
        if (j == 3)
          break;
        System.out.println(i + " - " + j);
      }
    }
  }
}

Résultat :
1 - 1
1 - 2
2 - 1
2 - 2

 

3.9.3.2. L'instruction continue

L'instruction continue s'utilise dans une boucle pour passer directement à l'itération suivante.

La syntaxe est de la forme :

continue;

L'instruction continue peut-être utilisée à l'intérieur des structures de contrôle de type boucles. Ŕ l'intérieur de la boucle, lorsqu'une instruction continue est rencontrée, le contrôle saute directement au début de la boucle pour aller à l'itération suivante : cela met immédiatement fin à l'exécution des instructions de l'itération en cours.

L'instruction continue est utilisée pour sauter une condition particulière et poursuivre l'exécution à la prochaine itération.

L'instruction continue est utilisable avec toutes les types de boucle en Java :

Exemple avec une boucle for

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    for (int i = 1; i < 5; i++) {
      if (i == 1 || i == 3) continue;
      System.out.println(i);
    }
  }
}

Résultat :
2
4

Exemple avec une boucle while

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    int i = 0; 
    while (i < 4) {
      i++;
      if (i == 1 || i == 3) continue;
      System.out.println(i);
    }
  }
}

Résultat :
2
4

Exemple avec une boucle do-while

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    int i = 0; 
    do {
      i++;
      if (i == 1 || i == 3) continue;
      System.out.println(i);
    } while (i < 4);
  }
}

Résultat :
2
4

Il est possible d'utiliser une instruction continue dans des boucles imbriquées. L'instruction continue permet d'ignorer le reste des traitements de l'itération en cours. Dans le cas de boucles imbriquées, elle passe à l'itération suivante de la boucle la plus interne.

Exemple :
public class Boucle {
 
  public static void main(String[] args) {
    for (int i = 1; i < 3; i++) {
      for (int j = 1; j < 5; j++) {
        if (j == 1 || j == 3)
          continue;
        System.out.println(i + " - " + j);
      }
    }
  }
}

Résultat :
1 - 2
1 - 4
2 - 2
2 - 4

Exemple avec une boucle for améliorée

Exemple ( code Java 5.0 ) :
import java.util.ArrayList;
import java.util.List;
 
public class Boucle {
 
  public static void main(String[] args) {
    List<Integer> liste = new ArrayList<Integer>();
    for(int i = 0; i < 5; i++) {
      liste.add(i);
    }
    
    for (Integer val : liste) {
      if (val % 2 == 0) continue;
      System.out.println(val);
    }
  }
}

Résultat :
1
3

 

3.9.3.3. L'utilisation de débranchement avec étiquette

break et continue peuvent s'exécuter avec des blocs nommés grâce à une étiquette.

Une étiquette est un nom suivi d'un caractère deux-points qui définit le début d'une instruction.

Remarque : l'utilisation dans le code des débranchements n'est pas recommandée.

Le langage Java permet d'exécuter des débranchements dans le code en utilisant des étiquettes (labels).

Il est possible de préciser une étiquette pour indiquer le point de retour lors de la fin du traitement déclenché par le break.

Une étiquette peut être utilisée pour identifier un bloc de code.

La syntaxe est de la forme :

etiquette:
  {
    instruction1;
    instruction2;
    ...
  }

Il est possible d'utiliser une instruction de débranchement break en la faisant suivre du nom d'une étiquette pour permettre un débranchement à la fin du bloc de code identifié par cette étiquette.

La syntaxe est de la forme :

break etiquette;
Exemple :
public class TestBreakAvecEtiquette {
 
  public static void main(String args[]) {
    boolean saut = true;
 
    bloc1: {
      System.out.println("debut bloc1");
      bloc2: {
        System.out.println("debut bloc2");
 
        bloc3: {
          System.out.println("debut bloc3");
 
          if (saut) {
            break bloc2;
          }
          System.out.println("fin bloc3");
        }
        System.out.println("fin bloc2");
      }
      System.out.println("fin bloc1");
    }
  }
}

Résultat :
debut bloc1
debut bloc2
debut bloc3
fin bloc1

Il est aussi possible de nommer une boucle à l'aide d'une étiquette pour permettre de l'interrompre même si cela est peu recommandé :

Exemple :
public class TestBreakAvecEtiquette {
 
  public static void main(String args[]) {
    int compteur = 0;
    boucle: while (compteur < 100) {
 
      for (int compte = 0; compte < 10; compte++) {
        compteur += compte;
        System.out.println("compteur = " + compteur);
        if (compteur > 20)
          break boucle;
      }
    }
  }
}

Résultat :
compteur = 0
compteur = 1
compteur = 3
compteur = 6
compteur = 10
compteur = 15
compteur = 21

 

3.9.3.4. L'instruction return

L'instruction return est utilisée pour sortir immédiatement de l'exécution d'une méthode et retourner le contrôle à l'appelant.

Elle peut être utilisée dans une méthode avec ou sans valeur de retour.

Pour les méthodes qui définissent un type de retour, l'instruction return doit être immédiatement suivie de la valeur de retour.

Exemple :
  public static long additionner(int a, int b) {
    return (long) a +b;
  }

Dans une telle méthode, toutes les branches de sortie de la méthode doivent avoir une instruction return qui fournit la valeur retournée pour la branche.

Exemple :
public class TestReturn {
 
  public static boolean estPair(int a) {
    if ( a % 2 == 0 ) return true;
  }
}

Résultat :
C:\java>javac TestReturn.java
TestReturn.java:5: error: missing return statement
  }
  ^
1 error

Pour régler l'erreur, il faut utiliser une instruction return dans la branche else de l'instruction if.

Exemple :
public class TestReturn {
 
  public static boolean estPair(int a) {
    if ( a % 2 == 0 ) return true;
        else return false;
  }
}

L'instruction return peut être utilisée à différents endroits dans une méthode, mais il faut toujours s'assurer qu'elle doit être la dernière instruction à être exécutée dans une méthode. Si ce n'est pas le cas, une erreur est émise par le compilateur.

L'instruction return ne doit pas nécessairement être la dernière instruction d'une méthode, mais elle doit être la dernière instruction à être exécutée dans une méthode.

Exemple :
public class TestReturn {
 
  public static long additionner(int a, int b) {
    return (long) a +b;
    System.out.println((long) a+b);
  }
}

Résultat :
C:\java>javac TestReturn.java
TestReturn.java:5: error: unreachable statement
    System.out.println((long) a+b);
    ^
TestReturn.java:6: error: missing return statement
  }
  ^
2 errors

Dans une méthode qui ne retourne pas de valeur, il est possible d'utiliser une instruction return pour arrêter immédiatement les traitements de la méthode.

Exemple :
public class TestReturn {
 
  public static void main(String[] args) {
    afficherPaireouImpaire(-1);
    afficherPaireouImpaire(2);
    afficherPaireouImpaire(3);
  }
  
  public static void afficherPaireouImpaire(int a) {
    if (a <= 0)
      return;
    if (a % 2 == 0)
      System.out.println(a + " est paire");
    else
      System.out.println(a + " est impaire");
  }
}

 

3.10. Les évolutions de l'instruction switch dans Java 14

L'instruction switch est présente dans le langage Java depuis la version 1.0. Sa syntaxe est inspirée de celle des langages C et C++ : elle permet d'exécuter du code selon la valeur qui lui est fournie.

Cependant, sa syntaxe n'est pas exempte de défaut, notamment :

Historiquement et jusqu'à Java 12, le mot clé réservé switch ne pouvait être utilisé que comme une structure de contrôle qui ne peut qu'exécuter un ou plusieurs traitements selon l'évaluation d'une valeur. A partir de Java 12 en preview et Java 14 en standard, l'instruction switch peut maintenant être utilisée comme une expression permettant de retourner une valeur. Plusieurs changements ont également été apportés à la syntaxe des déclarations des cas dans l'instruction switch.

L'instruction switch a évolué tout d'abord dans Java 12 dans une première version preview puis dans une seconde version preview en Java 13 :

Finalement en Java 14, via la JEP 361, ces évolutions deviennent des fonctionnalités standards en Java 14.

L'instruction switch peut dès lors être utilisée soit comme une structure du contrôle qui ne renvoie rien ou comme une expression qui renvoie une valeur.

Ces évolutions concernent la syntaxe et l'utilisation de l'instruction switch, dont voici un résumé :

Les types utilisables comme valeur à tester par l'instruction switch sont toujours les mêmes : byte, short, int, char, leurs wrappers, String et les énumérations.

 

3.10.1. La syntaxe de l'instruction switch avec l'opérateur arrow

Historiquement dans une instruction switch, chaque cas est défini avec le mot clé réservé case suivi d'une unique valeur puis du caractère deux points (case X: )

En plus de la forme historique de l'instruction switch, une nouvelle forme simplifiée nommée "case L ->" est introduite pour définir les cas d'une instruction switch. La nouvelle syntaxe utilise le mot clé réservé case et l'opérateur arrow : case X -> où x est la valeur du cas.

Dans une instruction switch, le code à la droite de l'opérateur -> ne peut être qu'une expression ou un traitement unique, un bloc de code ou la levée d'une exception.

Si plusieurs instructions doivent être exécutées pour un même cas, il faut obligatoirement les regrouper dans un bloc de code. Etant des blocs dédiés, il est possible d'utiliser le même nom de variable dans chacun des blocs.

La nouvelle syntaxe utilisant l'opérateur arrow peut être utilisée dans un switch en tant qu'instruction ou comme expression.

Le grand intérêt de cette nouvelle syntaxe avec l'opérateur arrow est que si la valeur correspond à celle de la clause case, alors les traitements ou la valeur à sa droite est uniquement exécutée. Il n'y a donc jamais de débordement (fall through) sur la clause case suivante, donc pas besoin de mettre une instruction break avec cette syntaxe.

 

3.10.2. L'utilisation de l'instruction switch comme une expression

Fréquemment, l'instruction switch est utilisée pour définir la valeur d'une variable en fonction de la valeur d'une autre.

Une expression est une séquence composée de valeurs littérales, de variables, d'opérateurs et d'invocations de méthodes qui une fois évaluée produit une valeur unique.

Le langage Java permet de construire des expressions composées à partir de diverses expressions plus petites tant que le type de données requis par une partie de l'expression correspond au type de données de l'autre.

Comme avec l'opérateur ternaire qui est une expression équivalente à une instruction if/else, l'instruction switch est améliorée pour pouvoir être utilisée comme une expression. L'instruction switch est toujours utilisables comme un traitement mais elle est aussi utilisable comme une expression.

La syntaxe de l'instruction switch évolue pour permettre son utilisation en tant qu'expression : comme toute expression, elle peut être utilisée partout où une expression peut être attendue. Par exemple, comme valeur d'affectation à une variable, comme valeur d'une instruction return, ...

Cela signifie qu'il n'est plus nécessaire de déclarer d'abord une variable, puis de lui attribuer une valeur dans chaque branche. Combiné à l'utilisation de la syntaxe avec l'opérateur arrow le code devient plus simple.

Utilisée comme une expression, l'instruction switch doit retourner une valeur pour toutes les valeurs possibles du type de la variable passée en paramètre.

 

3.10.2.1. L'instruction yield pour retourner une valeur

Dans la JEP 325, c'était une instruction break suivie de la valeur qui est utilisée pour préciser une valeur de retour.

Les retours ont indiqué que l'utilisation de l'instruction break pour indiquer la valeur à retourner pourrait être source de confusion. D'autant que le langage Java permet déjà l'utilisation de break (et continue) avec une étiquette pour effectuer un débranchement similaire à une forme de goto.

Dans la JEP 354, Java 13 a introduit le nouveau mot-clé contextuel yield qui doit être utilisé pour retourner une valeur dans un switch comme une expression avec la syntaxe utilisant l'opérateur arrow et quelque chose qui n'est pas une simple expression. C'est le cas, si le traitement est un bloc de code.

L'utilisation de l'instruction break (avec ou sans étiquette) et yield réduit l'ambiguďté lors de l'utilisation d'un switch comme une structure de contrôle ou comme expression : l'instruction break ne peut être utilisée qu'avec un switch comme instruction et l'instruction yield ne peut être utilisée qu'avec un switch comme expression.

Important : yield n'est pas un mot clé réservé mais comme var c'est un identifiant restreint. yield peut être utilisé comme un identifiant mais a un rôle particulier dans l'instruction switch.

 

3.10.2.2. Les contraintes

Quant l'instruction switch est une expression, elle doit obligatoirement se terminer par un point-virgule si elle est à la fin d'une ligne de code. Ceci est différent de l'utilisation comme instruction. Le compilateur émet une erreur si une instruction switch utilisée comme expression ne se termine pas par un point-virgule en fin de ligne.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
   
    int valeur = 2;
    String type = switch(valeur) {
      case 1  -> "un";
      case 2  -> "deux";
      default -> "hors limite";
    }
    System.out.println(type);
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:9: error: ';' expected
    }
     ^
1 error

 

3.10.2.2.1. Les Poly Expressions

L'utilisation d'une instruction switch comme une expression est une poly expression.

Cela signifie qu'elles n'ont pas de type définitif propre, mais peuvent être de plusieurs types. Les poly expressions les plus utilisées sont les lambdas : s -> s + " " qui peuvent être l'implémentation d'une Function ou d'un UnaryOperator avec des types génériques différents.

Si le type de la valeur de retour est précisé alors toutes les valeurs retournées de toutes les branches doivent respecter ce type.

Si le type de la valeur de retour n'est pas précisé, comme lors de l'utilisation de var, alors il est déterminé en trouvant le supertype le plus spécifique des types que les branches retournent.

 

3.10.2.2.2. L'exhaustivité

Lors de l'utilisation d'une instruction comme une structure de contrôle, il n'est pas obligatoire de prendre en compte toutes les valeurs possibles selon le type de la variable fournie à l'instruction switch.

Il est alors possible d'oublier involontairement de prendre en compte une ou plusieurs valeurs : cela ne va pas empêcher le code de se compiler mais le résultat obtenu ne sera peut-être pas celui attendu.

Cette problématique est aggravée avec l'utilisation de l'instruction switch comme une expression. Si l'exhaustivité n'était pas obligatoire, il faudrait renvoyer des valeurs par défaut sont le type de retour : cela conduirait sûrement à des erreurs subtiles et occasionnelles.

Pour éviter cela, le compilateur va vérifier que toutes les valeurs du type de la variable à évaluer sont prises en comptes dans les clauses case de manière explicite. Cela implique dans tous les cas, sauf celui d'une énumération où toutes les valeurs sont prises en compte, d'utiliser une clause default.

 

3.10.3. L'utilisation de valeurs multiples dans une clause case

Historiquement la clause case d'une instruction switch n'accepte qu'une seule valeur. Pour définir plusieurs valeurs, il est nécessaire d'utiliser le mécanisme fall-through.

Dans la version preview de Java 12, il est possible de préciser plusieurs valeurs dans une clause case en séparant chaque d'elle par une virgule. Les valeurs multiples sont utilisables dans la syntaxe traditionnelle et dans la syntaxe utilisant l'opérateur arrow.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 -> System.out.println("Sur le podium");
      default      -> System.out.println("Hors podium");
    }

Cette possibilité est utilisable en remplacement de l'utilisation du fall-though puisqu'elle est aussi utilisable avec la syntaxe historique.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 : System.out.println("Sur le podium"); break;
      default :      System.out.println("Hors podium");
    }

L'utilisation de valeurs multiples est aussi possible lorsque l'instruction switch est utilisée comme une expression.

Que cela soit avec la syntaxe historique :

Exemple ( code Java 14 ) :
    int position = 1;
    boolean monteSurPodium = switch (position) {
      case 1, 2, 3 : yield true;
      default      : yield false;
    };
    System.out.println(monteSurPodium);

Ou que cela soit avec la nouvelle syntaxe :

Exemple ( code Java 14 ) :
    int position = 1;
    boolean monteSurPodium = switch (position) {
      case 1, 2, 3 -> true;
      default      -> false;
    };
    System.out.println(monteSurPodium);

Avec la possibilité d'utiliser plusieurs valeurs dans un case, il ne sera probablement plus utile d'utiliser le fall-through.

 

3.10.4. Les 4 formes de l'utilisation de switch

L'instruction switch peut être utilisée comme :

L'instruction switch propose aussi deux syntaxes :

Par combinaison des deux syntaxes et des deux cas d'utilisation, l'instruction switch possède 4 formes d'utilisation.

L'utilisation d'un « : » dans une clause case n'implique pas forcement que le switch est utilisé comme une structure de contrôle et l'utilisation d'un opérateur arrow n'implique par forcement que le switch est utilisé comme une expression.

 

3.10.4.1. L'utilisation de switch comme structure de contrôle avec la syntaxe classique

La syntaxe historique de l'instruction switch est toujours valide.

Avant Java 12, l'instruction switch est un traitement impératif qui conditionne l'exécution de code selon la valeur d'une variable. Historiquement inspirée dans l'instruction switch en C, sa syntaxe impose quelques contraintes et peut être à l'origine de bugs subtiles :

Un usage courant de l'instruction switch est de déterminer une valeur à partir d'une autre. Comme l'instruction switch ne peut pas renvoyer de valeur, il faut utiliser une variable intermédiaire qui doit être assignée à chaque cas.

Exemple :
  public static void main(String[] args) {
    FeuTricolore feu = VERT;
    String action;
    switch(feu) {
    case VERT : 
      action = "Passage avec priorité à droite";
      break;
    case ORANGE : 
      action = "Stop";
      break;
    case ROUGE : 
      action = "Interdiction absolue de passer";
      break;
    default :
      action = "Passage avec priorité à droite"; 
    }    
    System.out.println(action);
  }

Résultat :
Passage avec priorité à droite

Une possibilité pour pallier à certains problèmes est d'utiliser l'instruction switch dans une méthode : cela permet de retourner une valeur et d'éviter d'avoir à utiliser des breaks.

Exemple :
  public static String obtenirAction(FeuTricolore feu) {
    switch(feu) {
    case VERT : 
      return "Poursuite de sa route avec priorité à droite";
    case ORANGE : 
      return "Stop";
    case ROUGE : 
      return "Interdiction absolue de passer";
    default :
      return "Priorité à droite"; 
    }        
  }

Le fall-through permet d'exécuter le même traitement pour plusieurs valeurs qui doivent être précisés dans leur propre cas.

Exemple :
  public static void main(String[] args) {
    FeuTricolore feu = VERT;
    String action;
    switch(feu) {
    case ORANGE : 
      action = "Stop";
      break;
    case ROUGE : 
      action = "Interdiction absolue de passer";
      break;
    case VERT : 
    default :
      action = "Passage avec priorité à droite"; 
    }    
    System.out.println(action);
  }

Résultat :
Passage avec priorité à droite

Il peut aussi être problématique car il est facile d'oublier une instruction break.

Exemple :
  public static void main(String[] args) {
    FeuTricolore feu = VERT;
    String action;
    switch(feu) {
    case VERT : 
      action = "Passage avec priorité à droite";
    case ORANGE : 
      action = "Stop";
      break;
    case ROUGE : 
      action = "Interdiction absolue de passer";
      break;
    default :
      action = "Passage avec priorité à droite"; 
    }    
    System.out.println(action);
  }

Résultat :
Stop

Dans l'exemple ci-dessous l'absence de l'instruction break dans le cas VERT conduit à un résultat erroné.

 

3.10.4.2. L'utilisation de switch comme expression avec la syntaxe classique

Il est possible d'utiliser la syntaxe traditionnelle de l'instruction switch qui utilise le caractère deux point dans les clauses case dans une instruction switch utilisée comme une expression. Pour désigner la valeur retournée pour un cas, il faut utiliser l'instruction yield suivie de la valeur souhaitée.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
   
    int valeur = 2;
    String libelle = switch(valeur) {
      case 1  : yield "un";
      case 2  : yield "deux";
      default : yield "hors limite";
    };
    System.out.println(libelle);
  }
}

Résultat :
deux

Une clause case peut contenir plusieurs instructions.

Exemple ( code Java 14 ) :
    int    valeur = 2;
    String libelle   = switch (valeur) {
      case 1:
        System.out.println("cas 1");
        yield "un";
      case 2: 
        System.out.println("cas2");
        yield "deux";
      default:
        yield "hors limite";
    };
    System.out.println(libelle);

Résultat :
cas 2
deux

Il est aussi possible d'utiliser un bloc de code dans une clause case.

Exemple ( code Java 14 ) :
    int    valeur = 2;
    String libelle   = switch (valeur) {
      case 1: {
        System.out.println("cas 1");
        yield "un";
      }
      case 2: { 
        System.out.println("cas 2");
        yield "deux";
      }
      default: {
        yield "hors limite";
      }
    };
    System.out.println(libelle);

Résultat :
cas 2
deux

L'utilisation d'un bloc de code permet comme tout bloc de code de regrouper plusieurs traitements qui seront exécutés si la valeur correspond au cas associé.

Il est possible de définir les mêmes variables dans plusieurs blocs puisqu'ils ne sont pas imbriqués.

Exemple ( code Java 14 ) :
    int    valeur = 2;
    String libelle   = switch (valeur) {
      case 1: {
        String val = "un";
        yield val;
      }
      case 2: {
        String val = "deux";
        yield val;
      }
      default:
        yield "hors limite";
    };
    System.out.println(libelle);

Résultat :
deux

Attention, avec la syntaxe historique, le fall-through est toujours utilisé par défaut.

Exemple ( code Java 14 ) :
    int    valeur = 2;
    String libelle   = switch (valeur) {
      case 1:
        System.out.println("cas 1");
        yield "un";
      case 2: 
        System.out.println("cas 2");
      default:
        yield "hors limite";
    };
    System.out.println(libelle);

Résultat :
cas 2
hors limite

 

3.10.4.3. L'utilisation de switch comme expression avec l'opérateur arrow

L'utilisation de la syntaxe avec l'opérateur arrow facilite l'utilisation de l'instruction switch comme une expression : la valeur retournée est celle directement indiquée à la droite de l'opérateur arrow.

Exemple ( code Java 14 ) :
    FeuTricolore feu    = VERT;
    String       action = switch (feu) {
      case VERT   -> "Passage avec priorité à droite";
      case ORANGE -> "Stop";
      case ROUGE  -> "Interdiction absolue de passer";
      default     -> "Passage avec priorité à droite";
    };
    System.out.println(action);

Résultat :
Passage avec priorité à droite

Attention, lorsque qu'une instruction switch est utilisée comme une expression, il faut obligatoirement terminer l'instruction par un point-virgule.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    FeuTricolore feu    = VERT;
    String       action = switch (feu) {
      case VERT   -> "Passage avec priorité à droite";
      case ORANGE -> "Stop";
      case ROUGE  -> "Interdiction absolue de passer";
      default     -> "Passage avec priorité à droite";
    }
    System.out.println(action);
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:11: error: ';' expected
    }
     ^
1 error

Une instruction switch utilisée comme une expression peut être utilisée partout où une expression du type retourné par le switch est attendue.

Exemple ( code Java 14 ) :
    FeuTricolore feu = VERT;
    System.out.println(switch (feu) {
      case VERT -> "Passage avec priorité à droite";
      case ORANGE -> "Stop";
      case ROUGE -> "Interdiction absolue de passer";
      default -> "Passage avec priorité à droite";
    });

Résultat :
Passage avec priorité à droite 

Lorsqu'une instruction switch est utilisée comme expression, elle doit toujours retourner une valeur ou lever une exception.

Exemple ( code Java 14 ) :
    FeuTricolore feu = VERT;
    String action = switch (feu) {
      case VERT   -> "Passage avec priorité à droite";
      case ORANGE -> "Stop";
      case ROUGE  -> "Interdiction absolue de passer";
      default     -> throw new IllegalArgumentException("Feu tricolore défaillant");
    };
    System.out.println(action);

Si le code à exécuter contient plusieurs lignes, alors il faut utiliser un bloc de code. Dans ce cas, pour retourner une valeur, il faut utiliser une instruction yield.

Exemple ( code Java 14 ) :
    FeuTricolore feu    = VERT;
    String       action = switch (feu) {
      case VERT   -> "Passage avec priorité à droite";
      case ORANGE -> "Stop";
      case ROUGE  -> "Interdiction absolue de passer";
      default     -> { 
        System.out.println("cas par defaut");
        yield "Passage avec priorité à droite";
      }
    };
    System.out.println(action);

 

3.10.4.4. L'utilisation de switch comme structure de contrôle avec l'opérateur arrow

En utilisant la syntaxe avec l'opérateur arrow dans un switch comme structure de contrôle, il ne peut y avoir qu'une seule instruction.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 -> System.out.println("Sur le podium"); 
      default      -> System.out.println("Hors podium");
    }

Résultat :
Sur le podium

Le compilateur émet une erreur si plusieurs instructions sont utilisées les unes à la suite des autres.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 -> System.out.println("Sur le podium"); 
                      System.out.println("Avec medaille");
      default      -> System.out.println("Hors podium");
    }

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:7: error: case, default, or '}' expected
                      System.out.println("Avec medaille");
                      ^
...
9 errors

Dans ce cas, il faut utiliser un bloc de code.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 -> {
        System.out.println("Sur le podium"); 
        System.out.println("Avec medaille");
      }
      default      -> System.out.println("Hors podium");
    }

Dans le bloc de code, il est possible d'utiliser l'instruction break pour sortir du bloc et donc de l'instruction puisqu'il n'y a pas de fall-though.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 -> {
        System.out.println("Sur le podium"); 
        System.out.println("Avec medaille");
        break;
      }
      default      -> System.out.println("Hors podium");
    }

En erreur est émise par le compilateur si des instructions suivent l'instruction break.

Exemple ( code Java 14 ) :
    int position = 1;
    switch (position) {
      case 1, 2, 3 -> {
        System.out.println("Sur le podium"); 
        System.out.println("Avec medaille");
        break;
        System.out.println("");
      }
      default      -> System.out.println("Hors podium");
    }

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:10: error: unreachable statement
        System.out.println("");
        ^
1 error

 

3.10.4.5. Le résumé des différentes formes d'utilisation

Il est possible de combiner les deux syntaxes de l'instruction switch pour une utilisation en tant que structure de contrôle ou comme expression : cela donne 4 formes d'utilisation. Des contraintes s'appliquent sur ces 4 formes ce qui rend leur utilisation plus complexe.

Le tableau ci-dessous résume les contraintes des différentes formes et cas d'utilisation :

Quel que soit l'utilisation

Structure de contrôle

Expression

Quel que soit la syntaxe

Valeurs multiples possible dans les cases

default facultatif (pas d'exhaustivité)

yield interdit

Exhaustivité obligatoire des valeurs à prendre en compte

return interdit
continue/break interdit

L'instruction doit se terminer par un « ; » en fin de ligne

Syntaxe classique

case X:

Fall-through par défaut

Une seule portée pour les variables

Chaque case peut avoir zéro ou plusieurs instructions suivies selon les besoins d'un break

return autorisé

continue/break autorisé

yield pour retourner une valeur

Chaque case peut avoir zéro ou plusieurs instructions mais doit finir par retourner une valeur avec yield ou lever une exception

Syntaxe avec l'opérateur arrow

case X ->

Pas de fall-through

Chaque case à sa propre portée

Chaque case doit avoir un traitement ou un bloc de code

continue/break autorisé dans un bloc de code

Chaque case doit retourner une valeur

yield pour retourner une valeur dans un bloc de code

 

3.10.5. Les situations illicites

Avec la nouvelle syntaxe et le nouveau rôle de l'instruction switch, plusieurs situations ne sont pas permises et provoqueront une erreur de la part du compilateur.

 

3.10.5.1. Des types différents retournés par les cases d'un switch comme expression

Lorsque le type de la variable d'affectation est déterminé, le compilateur vérifie que toutes les valeurs retournées par les clauses case sont bien du même type, quel que soit la syntaxe utilisée.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  : yield "un";
      case 2  : yield 2;
      default : yield "hors limite";
    };
    System.out.println(type);
    System.out.println(type.getClass());
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:7: error: incompatible types: bad type in switch expression
      case 2  : yield 2;
                      ^
    int cannot be converted to String
1 error

Exemple ( code Java 14 ) :
public class TestSwitch {
  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  -> "un";
      case 2  -> 2;
      default -> hors limite";
    };
    System.out.println(type);
    System.out.println(type.getClass());
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:7: error: incompatible types: bad type in switch expression
      case 2 -> 2;
                 ^
    int cannot be converted to String
1 error

Si le type de la variable n'est pas défini explicitement, une instruction switch peut retourner des valeurs de types différents. Dans cas, il faut impérativement déclarer la variable avec l'instruction var.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    int valeur = 2;
    var type = switch(valeur) {
      case 1  : yield "un";
      case 2  : yield 2;
      default : yield "hors limite";
    };
    System.out.println(type);
    System.out.println(type.getClass());
  }
}

Résultat :
C:\java> java TestSwitch
2
class java.lang.Integer

Attention : l'utilisation de cette fonctionnalité peut être source de difficultés et ne peut contourner les règles du typage statique de Java.

 

3.10.5.2. Dans un switch comme structure de contrôle, on ne peut pas retourner de valeur

Une erreur sera émise par le compilateur en cas de renvoie d'une valeur par l'instruction break dans un switch utilisée comme traitement.

Exemple ( code Java 14 ) :
public class TestSwitch {
  
  public static void main(String[] args) {
    int valeur = 2;
    String type; 
    switch(valeur) {
      case 1  : yield "un";
      case 2  : yield "deux";
      default : yield "hors limite";
    };
    System.out.println(type);
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:7: error: yield outside of switch expression
      case 1  : yield "un";
                ^
TestSwitch.java:8: error: yield outside of switch expression
      case 2  : yield "deux";
                ^
TestSwitch.java:9: error: yield outside of switch expression
      default : yield "hors limite";
                ^
3 errors

 

3.10.5.3. Dans un switch comme expression, il faut retourner une valeur après l'opérateur ->

Une erreur sera émise par le compilateur en cas de non renvoie d'une valeur par l'opérateur arrow « -> » dans une instruction switch utilisée comme une expression.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  -> "un";
      case 2  -> "deux";
      default -> System.out.println("hors limite");
    };
    System.out.println(type);
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:8: error: incompatible types: bad type in switch expression
      default -> System.out.println("hors limite");
                                   ^
    void cannot be converted to String
1 error

 

3.10.5.4. Dans un switch comme traitement, l'opérateur -> ne peut pas retourner de valeur

Une erreur sera émise par le compilateur en cas d'utilisation du renvoie d'une valeur par l'opérateur arrow « -> » dans une instruction switch qui n'est pas utilisée comme une expression.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type; 
    switch(valeur) {
      case 1  -> "un";
      case 2  -> "deux";
      default -> "hors limite";
    };
    System.out.println(type);
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:7: error: not a statement
      case 1  -> "un";
                 ^
TestSwitch.java:8: error: not a statement
      case 2  -> "deux";
                 ^
TestSwitch.java:9: error: not a statement
      default -> "hors limite";
                 ^
3 errors

 

3.10.5.5. Toutes les valeurs possibles doivent être prises en compte dans un switch utilisé comme expression

Toutes les valeurs possibles doivent être prises en compte explicitement et exhaustivement dans une instruction switch utilisée comme expression. Sauf si la valeur testée est une énumération et que toutes les valeurs sont utilisées dans une instruction case, sinon il faut utiliser une instruction default pour définir le cas par défaut qui concerne toutes les valeurs non explicitement définies dans un case.

Lors de l'utilisation d'une instruction switch comme une expression, le compilateur vérifie que toutes les valeurs possibles pour le type de la variable sont prises en compte dans une clause case. Si ce n'est pas le cas, le compilateur émet une erreur.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1 -> "un";
      case 2 -> "deux";
    };
    System.out.println(type);
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:5: error: the switch expression does not cover all possible input values
    String type = switch(valeur) {
                  ^
1 error

Prendre en compte toutes les valeurs possibles selon le type n'est pas toujours facile voire impossible : il est alors possible d'utiliser l'instruction default pour prendre en compte tous les autres cas.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  -> "un";
      case 2  -> "deux";
      default -> "hors limite";
    };
    System.out.println(type);
  }
}

L'exhaustivité de la prise en compte des valeurs est obligatoire lors de l'utilisation de l'instruction switch comme une expression : cela concerne aussi l'utilisation comme une expression avec la syntaxe historique. D'ailleurs concrètement, cela signifie qu'il faut presque toujours définir une clause par défaut dans une instruction switch utilisée comme expression.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  : yield "un";
      case 2  : yield "deux";
    };
    System.out.println(type);
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:7: error: the switch expression does not cover all possible input values
    String type = switch(valeur) {
                  ^
1 error

 

3.10.5.6. Il n'est pas possible de mixer les deux syntaxes d'une clause case

Une erreur sera émise par le compilateur en cas d'utilisation de la syntaxe historique utilisant « : » et la nouvelle syntaxe utilisant l'opérateur arrow « -> ».

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    int valeur = 2;
    String type; 
    switch(valeur) {
      case 1 -> type = "un";
      case 2  : type = "deux"; break;
      default : type = "hors limite"; break;
    };
    System.out.println(type);
  }
}

Résultat :
C:\java> javac TestSwitch.java
TestSwitch.java:8: error: different case kinds used in the switch
      case 2  : type = "deux"; break;
      ^
1 error

 

3.10.5.7. L'utilisation de l'instruction continue dans un switch

L'instruction continue peut être utilisée dans une instruction switch avec la syntaxe historique.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      switch (i) {
        case 1, 2, 3 :  System.out.println("Sur le podium");
        default : continue;
      }
    }
  }
}

Résultat :
C:\java>java TestSwitch
1
Sur le podium
2
Sur le podium
3
Sur le podium
4
5

Il n'est pas possible d'utiliser l'instruction continue dans un switch utilisé comme structure de contrôle utilisant la syntaxe avec l'opérateur arrow.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      switch (i) {
        case 1, 2, 3 -> System.out.println("Sur le podium");
        default -> continue;
      };
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: unexpected statement in case, expected is an expression,
 a block or a throw statement
        default -> continue;
                   ^
1 error

Il est cependant possible d'utiliser l'instruction continue dans un bloc de code.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      switch (i) {
        case 1, 2, 3 -> System.out.println("Sur le podium");
        default -> { continue; }
      };
      System.out.println("suite");
    }
  }
}

Résultat :
C:\java>java
TestSwitch
1
Sur le podium
suite
2
Sur le podium
suite
3
Sur le podium
suite
4
5

Dans tous les cas, il n'est pas possible d'utiliser l'instruction continue lorsqu'une instruction switch est utilisée comme une expression.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      String libelle = switch (i) {
        case 1, 2, 3 -> "Sur le podium";
        default -> continue;
      };
      System.out.println(libelle);
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: illegal start of expression
        default -> continue;
                   ^
TestSwitch.java:10: error: case, default, or '}' expected
        default -> continue;
                           ^
2 errors

Ce n'est pas possible non plus dans un bloc de code.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      String libelle = switch (i) {
        case 1, 2, 3 -> "Sur le podium";
        default -> { continue; }
      };
      System.out.println(libelle);
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: attempt to continue out of a switch expression
        default -> { continue; }
                     ^
1 error

Il n'est pas possible non plus d'utiliser l'instruction continue dans un switch utilisé comme une expression avec la syntaxe historique.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      String libelle = switch (i) {
        case 1, 2, 3 :  yield "Sur le podium";
        default : continue;
      };
      System.out.println(libelle);
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: attempt to continue out of a switch expression
        default -> { continue; }
                     ^
1 error

 

3.10.5.8. L'utilisation de l'instruction break dans un switch

Il n'est pas possible d'utiliser l'instruction continue dans un switch utilisé comme structure de contrôle utilisant la syntaxe avec l'opérateur arrow.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      switch (i) {
        case 1, 2, 3 -> System.out.println("Sur le podium");
        default -> break;
      };
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: unexpected statement in case, expected is an expression,
 a block or a throw statement
        default -> break;
                   ^
1 error

Il est possible d'utiliser l'instruction break dans un bloc de code mais elle ne permet pas d'interrompre la boucle.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);     
      switch (i) {
        case 1, 2, 3 -> System.out.println("Sur le podium");
        default -> { break; }
      };
      System.out.println("suite");      
    }
    System.out.println("fin");
  }
}

Résultat :
C:\java>java
TestSwitch
Sur le podium
1
Sur le podium
2
Sur le podium
3
4
5
fin

Pour le faire, il est possible d'utiliser un break avec un étiquette dans un bloc de code. L'utilisation de cette forme de break n'est cependant pas recommandée.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    suite:
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);     
      switch (i) {
        case 1, 2, 3 -> System.out.println("Sur le podium");
        default -> { break suite; }
      };
      System.out.println("suite");      
    }
    System.out.println("fin");
  }
}

Résultat :
C:\java>java TestSwitch
1
Sur le podium
suite
2
Sur le podium
suite
3
Sur le podium
suite
4
fin

Dans tous les cas, il n'est pas possible d'utiliser l'instruction break lorsqu'une instruction switch est utilisée comme une expression.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      String libelle = switch (i) {
        case 1, 2, 3 -> "Sur le podium";
        default -> break;
      };
          System.out.println(libelle);
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: illegal start of expression
        default -> break;
                   ^
TestSwitch.java:10: error: case, default, or '}' expected
        default -> break;
                        ^
2 errors

Ce n'est pas possible non plus dans un bloc de code.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      String libelle = switch (i) {
        case 1, 2, 3 -> "Sur le podium";
        default -> { break; };
      };
      System.out.println(libelle);
    }
    System.out.println("fin");
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: case, default, or '}' expected
        default -> { break; };
                             ^
1 error

L'utilisation dans un bloc de code d'une instruction break avec une étiquette provoque la même erreur.

Il n'est pas possible non plus d'utiliser l'instruction break dans un switch utilisé comme une expression avec la syntaxe historique.

Exemple ( code Java 14 ) :
public class TestSwitch {
 
  public static void main(String[] args) {
 
    for (int i = 1; i <= 5; i++) {
      System.out.println(i);
      String libelle = switch (i) {
        case 1, 2, 3 :  yield "Sur le podium";
        default : break;
      };
      System.out.println(libelle);
    }
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:10: error: attempt to break out of a switch expression
        default : break;
                  ^
1 error

La même erreur est émise avec un break avec etiquette.

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:11: error: attempt to break out of a switch expression
        default : break suite;
                  ^
1 error

 

3.10.5.9. L'utilisation de return dans un switch comme expression

Il n'est pas possible d'utiliser l'instruction return dans un switch utilisé comme expression.

Ni avec la syntaxe historique

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  : yield "un";
      case 2  : return;
    };
    System.out.println(type);
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:9: error: attempt to return out of a switch expression
      case 2  : return;
                ^
1 error

Ni avec la syntaxe utilisant l'opérateur arrow.

Exemple ( code Java 14 ) :
public class TestSwitch {

  public static void main(String[] args) {
    int valeur = 2;
    String type = switch(valeur) {
      case 1  -> "un";
      case 2  -> return;
    };
    System.out.println(type);
  }
}

Résultat :
C:\java>javac TestSwitch.java
TestSwitch.java:9: error: illegal start of expression
      case 2  -> return;
                 ^
TestSwitch.java:9: error: case, default, or '}' expected
      case 2 -> return;
                       ^
2 errors

 

3.11. Les tableaux

Les tableaux permettent de stocker un ensemble fini d'éléments d'un type particulier. L'accès à un élément particulier se fait grâce à son indice. Le premier élément d'un tableau possède l'indice 0.

Même si leur déclaration est spécifique, ce sont des objets : ils sont donc dérivés de la classe Object. Il est possible d'utiliser les méthodes héritées telles que equals() ou getClass().

 

3.11.1. La déclaration et l'allocation des tableaux

La déclaration d'un tableau à une dimension requiert un type, le nom de la variable permettant de faire référence au tableau et une paire de crochets. Java permet de placer les crochets sur le type ou sur le nom de la variable dans la déclaration.

L'allocation de la mémoire et donc l'obtention d'une référence pour accéder au tableau se fait en utilisation l'opérateur new, suivi d'une paire de crochets qui doit contenir le nombre maximum d'éléments que peut contenir le tableau.

La déclaration et l'allocation peut se faire sur une même ligne ou sur des lignes distinctes.

Exemple :
int tableau[] = new int[50]; // déclaration et allocation

// OU 

int[] tableau = new int[50];

// OU

int tab[];         // déclaration
tab = new int[50]; // allocation

Pour passer un tableau à une méthode, il suffit de déclarer le paramètre dans la signature de la méthode

Exemple :
public void afficher(String texte[]){
  // ... 
}

Les tableaux sont toujours transmis par référence puisque ce sont des objets.

Java ne supporte pas directement les tableaux à plusieurs dimensions : il faut déclarer un tableau de tableau en utilisant une paire de crochet pour chaque dimension.

Exemple :
float tableau[][] = new float[10][10];

La taille des tableaux de la seconde dimension peut ne pas être identique pour chaque occurrence.

Exemple :
int dim1[][] = new int[3][];
dim1[0]      = new int[4];
dim1[1]      = new int[9];
dim1[2]      = new int[2];

Chaque élément du tableau est initialisé selon son type par l'instruction new : 0 pour les numériques, '\0' pour les caractères, false pour les booléens et null pour les chaînes de caractères et les autres objets.

 

3.11.2. L'initialisation explicite d'un tableau

Exemple :
int tableau[5]    = {10, 20, 30, 40, 50};
int tableau[3][2] = {{5, 1}, {6, 2}, {7, 3}};

La taille du tableau n'est pas obligatoire si le tableau est initialisé à sa création.

Exemple :
int tableau[] = {10, 20, 30, 40, 50};

Le nombre d'éléments de chaque ligne peut ne pas être identique :

Exemple :
int[][] tabEntiers = {{1, 2, 3, 4, 5, 6},
                     {1, 2, 3, 4},
                     {1, 2, 3, 4, 5, 6, 7, 8, 9}};

 

3.11.3. Le parcours d'un tableau

La variable length retourne le nombre d'éléments du tableau. Il est alors possible d'utiliser une boucle pour itérer sur chacun des éléments du tableau.

Exemple :
for (int i = 0; i < tableau.length; i ++) {
  // ...
}

Un accès a un élément d'un tableau qui dépasse sa capacité, lève une exception du type java.lang.arrayIndexOutOfBoundsException.

A partir de Java 5, le parcours de l'intégralité des éléments d'un tableau peut être réalisé en utilisant la version améliorée de l'instruction for. La syntaxe générale est de la forme :

for ( type element_courant : type[] ) {
  // code à exécuter sur l'élément courant identifié par la variable element_courant
}

Exemple :
    String[] chaines = {"element1","element2","element3"};
    for (String chaine : chaines) {
      System.out.println(chaine);
    }

 

3.12. Les conversions de types

Lors de la déclaration, il est possible d'utiliser un cast :

Exemple :
int   entier   = 5;
float flottant = (float) entier;

La conversion peut entrainer une perte d'informations.

Il n'existe pas en Java de fonction pour convertir : les conversions de type se font par des méthodes. La bibliothèque de classes API fournit une série de classes qui contiennent des méthodes de manipulation et de conversion de types élémentaires.

Classe Rôle
String pour les chaînes de caractères Unicode
Integer pour les valeurs entières (integer)
Long pour les entiers longs signés (long)
Float pour les nombres à virgule flottante (float)
Double pour les nombres à virgule flottante en double précision (double)

Les classes portent le même nom que le type élémentaire sur lequel elles reposent avec la première lettre en majuscule.

Ces classes contiennent généralement plusieurs constructeurs ou des méthodes statiques pour obtenir des instances.

 

3.12.1. La conversion d'un entier int en chaîne de caractères String

Exemple :
int    i        = 10;
String montexte = new String();
montexte = montexte.valueOf(i);

Des surcharges de la méthode valueOf() sont également définies pour des arguments de type boolean, long, float, double et char

 

3.12.2. La conversion d'une chaîne de caractères String en entier int

Exemple :
String  montexte  = "10";
Integer monnombre = new Integer(montexte);
int     i         = monnombre.intValue(); // conversion d'Integer en int

 

3.12.3. La conversion d'un entier int en entier long

Exemple :
int     i         = 10;
Integer monnombre = new Integer(i);
long    j         = monnombre.longValue();

 

3.13. L'autoboxing et l'unboxing

L'autoboxing permet de transformer automatiquement une variable de type primitif en un objet du type du wrapper correspondant. L'unboxing est l'opération inverse. Cette nouvelle fonctionnalité est spécifiée dans la JSR 201 et intégrée dans Java 1.5.

Par exemple, jusqu'à la version 1.4 de Java pour ajouter des entiers dans une collection, il était nécessaire d'encapsuler chaque valeur dans un objet de type Integer.

Exemple :
import java.util.*;

public class TestAvantAutoboxing {

  public static void main(String[] args) {
    List liste = new ArrayList();
    Integer valeur = null;
    for(int i = 0; i < 10; i++) {
      valeur = new Integer(i);
      liste.add(valeur);
    }
  }
}

Avec Java 1.5, l'encapsulation de la valeur dans un objet n'est plus obligatoire car elle sera réalisée automatiquement par le compilateur.

Exemple (java 1.5) :
import java.util.*;

public class TestAutoboxing {

  public static void main(String[] args) {
    List liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(i);
    }    
  }
}

 


2. Les notions et techniques de base en Java 4. La programmation orientée objet Imprimer Index Index avec sommaire Télécharger le PDF    
Développons en Java
v 2.40   Copyright (C) 1999-2023 .