Java non-objet

Vue d'en haut

Un programme java est constitué d'un ou plusieurs package.
Chaque package contient la définition d'un ou plusieurs types référence (en général classes ou interfaces).
Un type référence est constitué par la définition de ses membres.
Parmi les membres des classes, on a les méthodes, qui contiennent la majorité du code qu'on écrit.

Vue d'en bas

Unités syntaxiques

Un fichier java contient la définition d'un ou plusieurs type référence.

A l'intérieur de la définition d'un type référence, on trouve des membres tels que des champs, des constructeurs, des méthodes.
Les méthodes (qui contiennent la majorité du code) sont des blocs de code java composés d'instructions (statements).
Ces instructions sont elles-mêmes composées d'identifiants (noms des variables, classes etc.), d'opérateurs (addition, concaténation etc.), d'expressions.
On appelle unités sytaxiques ou jetons (tokens) les briques élémentaires d'un programme.
Ces jetons sont eux-mêmes composés de caractères (unicode).

Unicode

Les programmes java utilisent le codage unicode ; il est donc possible d'utiliser des caractères accentués, des lettres grecques ou de tout autre alphabet dans le code (noms des variables, méthodes, classes).
Chaque caractère d'un programme java est représenté par 16 bits.
\u0020 à \u007E         code ASCII, Latin-1
\u00AE                  ©
\u00BD                  / la barre de fraction ...
\u0000 à \u1FFF         zone alphabets
\u0370 à \u03FFF        alphabet grec
...
Voir http://www.unicode.org

Espaces, casse et commentaires

Java est sensible à la casse (case sensitive), donc les variables toto et Toto sont différentes.
Par exemple le mot-clé for est différent de For ou FOR (en pratique, on évite de compter sur la casse pour différencier des identifiants car source de bug).

Les conventions suivantes sont utilisées :
- Les noms des classes et interfaces commencent par une majuscule et sont camelCased - ex : UneClasse.
- Les noms des méthodes commencent par une minuscule et sont aussi camelCased - ex : uneMéthode()
- Les noms de package sont en minuscule - ex : package1.
- Les noms des constantes sont en majuscule, avec des underscore si besoin - ex : UNE_CONSTANTE

Java ignore les espaces, tabulations, retours à la ligne, sauf s'ils sont entre simples ou doubles quotes, dans des valeurs littérales de chaînes ou de caractères.
Les commentaires utilisent la syntaxe de C :
    //          simple ligne
    /*  */      multilignes
Les commentaires multilignes commençant par /** sont utilisés pour générer automatiquement de la documentation.
    /**  */     javadoc, voir la page officielle.

Identifiants, mots réservés, valeurs littérales

Un identifiant (identifier) est une suite de caractères qui représente quelque chose dans un programme java (classe, méthode, paramètre, variable).
Un identifiant commence par une lettre, un underscore (_) ou un symbole unicode représentant une monnaie (currency symbol) tel que $, €, £...
Un identifiant ne peut pas commencer par un chiffre.
Les caractères suivants d'un identifiant peuvent être des chiffres, lettres, underscore ou caractères représentant une monnaie.
Exemples d'identifiants valides :
    i
    theCurrentTime
    the_current_time
    θ
    $18
Par convention, on évite d'utiliser les symboles unicode représentant une monnaie car ils sont utilisés par des outils tels que les compilateurs, préprocesseurs etc.

Un identifiant ne peut pas prendre la valeur des mots qui font partie du langage lui-même, les mots réservés.
On a déjà vu quelques mots réservés (reserved words) : class, interface, void, static...
Liste complète pour java 17 (même liste que pour java 11) :
abstract    continue    for         new         switch
assert      default     if          package     synchronized
boolean     do          goto        private     this
break       double      implements  protected   throw
byte        else        import      public      throws
case        enum        instanceof  return      transient
catch       extends     int         short       try
char        final       interface   static      void
class       finally     long        strictfp    volatile
const       float       native      super       while
Certains mots réservés peuvent prendre des significations différentes suivant le contexte, par exemple default et final.

Les valeurs littérales sont des valeurs qui apparaissent directement en dur dans le code java.
La syntaxe diffère suivant le type, on va les voir en détail pour chaque type
Exemples :
1           // int
1.0         // double
1d          // double
1.0f        // float
1f          // float
'1'         // character
"one"       // String
true        // boolean
null        

Types primitifs et types références

En java, il existe deux sortes de variables qui ont des comportements très différents : Types primimtif et référence Java est un langage fortement typé ; toute variable doit avoir un type.
Le typage est statique : toute variable doit être déclarée avant d'être utilisée, et cette déclaration comporte la déclaration de son type.
Le typage est vérifié à la compilation, donc pas d’erreur à l’exécution due à une erreur de type, mais un changement de type hasardeux est toujours possible...

Les types primitifs

Les types primitifs sont au nombre de 8 :

Category Types Size (bits) Min Value Max Value Precision Example
Integer byte 8 -128 127 From +127 to -128 byte b = 65;
char 16 0 216-1 All Unicode characters char c = 'A';
char c = 65;
short 16 -215 215-1 From +32 767 to -32 768 short s = 65;
int 32 -231 231-1 From +2 147 483 647 to -2 147 483 648 int i = 65;
long 64 -263 263-1 From +9 223 372 036 854 775 807 to -9 223 372 036 854 775 808 long l = 65L;
Floating-point float 32 2-149 (2-2-23)·2127 From 3.402 823 5 E+38 to 1.4 E-45 float f = 65f;
double 64 2-1074 (2-2-52)·21023 From 1.797 693 134 862 315 7 E+308 to 4.9 E-324 double d = 65.55;
Other boolean 1 -- -- false, true boolean b = true;
void -- -- -- -- --

(source : https://en.wikibooks.org/wiki/Java_Programming/Primitive_Types)

Java n'est donc pas purement objet puisqu'on manipule des variables qui ne sont pas des objets.

A chaque type primitif correspond une classe de java.lang permettant d'encapsuler une variable d'un type primitif donné dans un objet.

boolean

Peuvent prendre deux valeurs : true ou false.
ATTENTION : contrairement à d'autres langages, les booléens ne peuvent pas être convertis dans d'autres types.
Le code suivant n'est pas valide :
if(!myVar){ ... }
Il faut explicitement faire :
if(myVar != null){ ... }

char

Un char représente un caractère unicode. On lui affecte une valeur en utilisant des simples quotes :
char c = 'r';
Un char peut contenir tout caractère unicode, en utilsant \u :
char aleph = '\u0500';
Certains caractères spéciaux sont exprimés en utilisant un antislash (\) suivi d'un code. On parle d'escape sequence.
Ces escape sequences sont aussi valables dans les chaînes de caractères (strings)
Escape Sequence Description
\t Insert a tab in the text at this point.
\b Insert a backspace in the text at this point.
\n Insert a newline in the text at this point.
\r Insert a carriage return in the text at this point.
\f Insert a formfeed in the text at this point.
\' Insert a single quote character in the text at this point.
\" Insert a double quote character in the text at this point.
\\ Insert a backslash character in the text at this point.
\xxxx Insert a Latin-1 character with the encoding xxx, where xxx is an octal (base 8) number between 000 and 377.
\uxxxx The Unicode character with encoding xxxx, where xxxx is four hexadecimal digits.
(source https://docs.oracle.com/javase/tutorial/java/data/characters.html)

On peut par exemple écrire :
char apostrophe = '\'', backslash = '\\';
Pour travailler sur des chars, java fournit une classe Characters, qui fournit des méthodes utilitaires telles que isDigit(), isLowerCase()

Les types entiers

Concerne les types java byte, short, int et long, qui diffèrent par la taille mémoire qu'ils occupent.
Tous ces types sont signés, java ne possède pas d'équivalent des types unsigned en C.
En général, on utilise le type int, dont la valeur littérale s'écrit "normalement" :
int i = 9874;
Pour affecter une valeur littérale à un long, elle doit finir par l ou L.
long myLong = 123L;
Si on utilise en général la base 10, on peut aussi exprimer les types entiers en base 8 (octal) ou 16 (hexadécimal) en faisant précéder la valeur littérale par 0 ou 0x:
int n1 = 0xff;
long n2 = 0xffL;
System.out.println("n1 = " + n1);
System.out.println("n2 = " + n2);
Résultat :
n1 = 255
n2 = 255
ATTENTION : si la valeur maximale est dépassée, java ne le signale pas (comportement circulaire), nous sommes responsables d'utiliser un type correspondant aux calculs
Par exemple,
byte b1 = 127, b2 = 1; // La valeur max pour un byte est 127
byte sum = b1 + b2; // sum = -128, qui est la plus petite valeur pour un byte
Pour travailler sur les types entiers, on dispose des classes Byte, Short, Integer et Long, qui fournissent des méthodes utilitaires.
Chacune de ces classes fournit aussi 2 constantes, MIN_VALUE et MAX_VALUE, qui indiquent les valeurs extrèmes que peut prendre le type correspondant.

Les types de nombres réels

java définit les types float (32 bits) et double (64 bits).
Quelques exemples de valeurs littérales pour des doubles :
1.2314
1.0     // attention 1 représente un entier et 1.0 un réel
1d
1D
.01
1.2345e2    // = 123.45 (notation scientifique, le e peut aussi être en majuscule)
1.2345e-2    // = 0.012345
Par défaut, on travaille avec des doubles. Pour définir un float, il faut faire suivre de f ou F la valeur littérale, par ex float a = 10.2f;.
Ces types peuvent aussi prendre 4 valeurs spéciales : infinité négative, positive, zéro et Nan (not a number).
Les classes Double et Float fournissent des méthodes (notamment pour convertir dans d'autres types) et des constantes aidant à les manipuler.

Les chaînes de caractères (strings)

Les strings sont représentées par la classe java.lang.String, ce n'est donc pas un type primitif.
Mais l'usage des strings est tellement courant que java permet une syntaxe spéciale pour l'affectation :
On peut écrire String s = "abcd" à la place de String s = new String("abcd").
Les valeurs littérales pour les strings utilisent des doubles quotes.
Les strings utilisent les mêmes escape sequences que les chars.
Les strings sont traitées dans une page à part.

Conversion entre les types

Les conversions entre les types sont parfois possible ; certaines conversions implicites (sans type cast) sont possibles, obéissent à des règles précises, voir la doc d'Oracle.
On distingue les conversions vers un type plus grand (widening conversion) - par ex de int vers long et les conversions vers des types plus petits (narrowing conversion).

Pour forcer une conversion, il faut utiliser le type casting avec des parenthèses :
int i = 13; byte b = (byte) i;

Tableau résumé
N : la conversion n'est pas possible
Y : widening conversion, faite automatiquement par java
C : narrowing conversion, demande un type casting explicite
Y* : widening conversion, mais perte d'information possible
            VERS
DEPUIS      boolean byte    short   char    int     long    float   double
boolean     -       N       N       N       N       N       N       N
byte        N       -       Y       C       Y       Y       Y       Y
short       N       C       -       C       Y       Y       Y       Y
char        N       C       C       -       Y       Y       Y       Y
int         N       C       C       C       -       Y       Y*      Y
long        N       C       C       C       C       -       Y*      Y*
float       N       C       C       C       C       C       -       Y
double      N       C       C       C       C       C       C       -
En utilisant jshell, expériementer la conversion de types, en faisant par exemple
1 / 2
1.0 / 2
1 / 2.0
(pour jshell, voir la page "Outils java")

Expressions et opérateurs

Jusqu'à présent on a vu les types primitifs qu'un programme peut manipuler et comment leur affecter des valeurs littérales.
On a aussi utilisé des variables (noms symboliques qui représentent ces valeurs).

Le niveau suivant dans la strcture d'un programme java est constitué par les expressions.
Une expression est une combinaison de symboles dont l'interpréteur java peut calculer la valeur.
Les expressions les plus simples sont les expressions primaires, qui sont soit une variable soit une valeur litérale.
Exemples :
1.3     // valeur littérale d'un float ou d'un double
false   // valeur littérale d'un booléen
somme   // une variable

Elles peuvent être ensuite combinées avec des opérateurs pour former des expressions composées.
Par exemple
somme = 1.3;
combine 2 expressions primaires (somme et 1.3) pour fabriquer une expression d'assignement (dont la valeur est celle de l'opérande de droite - voir plus loin les instructions expression).

Les opérateurs peuvent être utilisés avec des expressions aussi complexes que l'on veut :
somme = 1 + 2 + 3 * 1.2 + (4 + 8)/3.0;
Prio    A   Opérateur   Types opérandes         Description
-------------------------------------------------------------------------------------
16      L   .           object, member          Object member access
            [ ]         array, int              Array element access
            ( args )    method, arglist         Method invocation
            ++, --      variable                Post-increment, post-decrement
15      R   ++, --      variable                Pre-increment, pre-decrement
            +, -        number                  Unary plus, unary minus
            ~           integer                 Bitwise complement
            !           boolean                 Boolean NOT
14      R   new         class, arglist          Object creation
            ( type )    type, any               Cast (type conversion)
13      L   *, /, %     number, number          Multiplication, division, remainder
12      L   +, -        number, number          Addition, subtraction
            +           string, any             String concatenation
11      L   <<          integer, integer        Left shift
            >>          integer, integer        Right shift with sign extension
            >>>         integer, integer        Right shift with zero extension
10      L   <, <=       number, number          Less than, less than or equal
            >, >=       number, number          Greater than, greater than or equal
            instanceof  reference, type         Type comparison
9       L   ==          primitive, primitive    Equal (have identical values)
            !=          primitive, primitive    Not equal (have different values)
            ==          reference, reference    Equal (refer to same object)
            !=          reference, reference    Not equal (refer to different objects)
8       L   &           integer, integer        Bitwise AND
            &           boolean, boolean        Boolean AND
7       L   ^           integer, integer        Bitwise XOR
            ^           boolean, boolean        Boolean XOR
6       L   |           integer, integer        Bitwise OR
            |           boolean, boolean        Boolean OR
5       L   &&          boolean, boolean        Conditional AND
4       L   ||          boolean, boolean        Conditional OR
3       R   ? :         boolean, any            Conditional (ternary) operator
2       R   =           variable, any           Assignment
            *=, /=, %=, variable, any           Assignment with operation
            +=, -=, <<=,
            >>=, >>>=,
            &=, ^=, |=
1       R   →           arglist, method body    lambda expression

Prio = Priorité (ou Précédence) : lorsque plusieurs opérateurs sont combinés dans une expression sans utiliser de parenthèses.
Ex : On voit que * a une priorité supérieure à +
Donc 12+4*3 est interprété comme : 12 + (4 * 3)

A = Associativité - peut être gauche à droite (L) ou de droite à gauche (R)
Ex : 72 / 2 / 3 est traité comme (72 / 2) / 3 car l'opérateur / est associatif de gauche à droite.
Mais x = y = z = 17 est traité comme x = (y = (z = 17)) car l'opérateur = est associatif de droite à gauche (et dans l'affectation, le membre de gauche reçoit la valeur du membre de droite).
Certains opérateurs ne sont pas associatifs. Par exemple, les expressions (x <= y <= z) et x++-- sont invalides.

Types opérandes = A quel type(s) de variables s'appliquent les opérateurs

Voir https://docs.oracle.com/javase/tutorial/java/nutsandbolts/opsummary.html et https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html

Quelques exemples :
int a = 2;
a++;       // a = a + 1
a+=2;      // a = a + 2

double b = 10;
b/=3;       // b = b / 3
b = (a > 4 ? 10 : 11);      // if a > 4 then b = 10 else b = 11

// pre-increment and post-increment
int i = 1;
int j = ++i; // i = 2 et j = 2
int i = 1;
int j = i++; // i = 2 et j = 1

// modulo
int c = 14 % 12;    // c = 2
// 2h de l'après-midi est la même chose que 14h

Concaténation de chaînes

Cas particulier, l'opérateur + s'applique aussi aux chaînes (instances de la classe String).
System.out.println("Quotient : " + 7/3.0f);   // Quotient : 2.3333333
System.out.println("Somme : " + 7 + 3);       // Somme : 73
System.out.println("Somme : " + (7 + 3));     // Somme : 10
Remarquer les conversions automatiques de types ainsi que les parenthèses nécessaires pour l'addition
Utilise la méthode toString() des objets.
Plus de détails dans la page sur les String

Instructions (statements)

L'instruction est l'unité d'exécution de base. Contrairement aux expressions, les instructions n'ont pas de valeur (leur type est void).

Illustration :
jshell> var a = System.out.println("Hello World!");
|  Error:
|  cannot infer type for local variable a
|    (variable initializer is 'void')
|  var a = System.out.println("Hello World!");
|  ^-----------------------------------------^

Java exécute par défaut séquentiellement les instructions mais définit plusieurs instructions pour contrôler le flot d'exécution, modifiant de manière précisément définie le flot séquentiel.
Les instructions sont séparées par des ; (point-virgule) ou regroupées entre des accolades { ... }

Les instructions expression (expression statements)

Certaines expressions (celles qui, en plus de renvoyer une valeur, modifient l'état du programme) peuvent constituer des instructions.
Les expressions pouvant constituer des instructions sont : Par exemple :
a = 3;          // assignation
x *= 2;         // assignation avec opération
i++; ++i;       // pré et post-incrément
System.out.println("Statement"); // appel d'une méthode
Exercice : Compilez et exécutez Expressions.java, et bien comprendre ce qui est affiché.
Le fait que des choses comme l'affectation soient des expressions peut surprendre mais est très utile :
while((line = in.readLine()) != null) { ... }
Ici, (line = in.readLine()) est une expression, dont la valeur est ensuite utilisée pour faire une comparaison.

Les instructions composées (compound statements)

Sont composées d'un nombre quelconque d'instructions regroupées par des accolades.
Elles peuvent être utilisées partout où la syntaxe java attend une instruction simple.
for(int i=0; i < 10; i++){
    a[i]++;     // le corps de cette boucle est une instruction composée
    b[i]++;     // constituée de 2 instructions
}
A noter : on peut isoler une partie du code entre des accolades (réduit la visibilité des variables à une portion de code).
    // dans une méthode
    {
        int a = 4;
    }
    // ici, a n'est pas défini

L'instruction vide (empty statement)

Ne fait rien, mais parfois pratique :
for(int i=0; i < 10; i++)
    /* empty */;

Les instructions labelisées (labeled statements)

Ce sont des instructions commençant par un label et suivi de : (deux points)
Utilisés par break et continue (très utiles dans certains cas).
iloop: for(int i=0; i < 10; i++){
    jloop: for(int j=0; j < 10; j++){
        break iloop;
    }
}

Les instructions de déclaration de variable locale

int i;
String s;
java ne permet pas d'utiliser une variable locale qui n'a pas été initialisée.
DANGER : la règle pour les variable locales est différente de la règle pour les variables de classe ou d'instance, qui ont une valeur par défaut si elles ne sont pas initialisées.

On fait souvent l'initialisation en même temps que la déclaration :
int i = 0;
String s = "string";
int[] a = {i+1, i+2, i+3}; // on verra plus loin les tableaux
La partie de droite d'une initialisation peut être tout type d'expression, aussi complexe soit elle.

Il est aussi possible de déclarer (et initialiser) plusieurs variables du même type dans la même expression :
int i, j, k;
float x = 1.0, y = 3.5;
A noter que les variables peuvent être déclarées n'importe où dans le code (plus souple que C qui impose que ce soit au début d'un bloc ou d'une fonction).

La portée des variables (scope) est limitée à la méthode ou bloc où elles sont définies.
void méthode(){
    int i = 0;
    while(i < 10){
        int j = 0;
        i++;
    }
    System.out.println(i); // 10
    System.out.println(j); // erreur, car j n'est plus définie hors de son bloc de définition
}

Déclaration de variable avec var

Depuis java 10, il est possible d'utiliser var pour déclarer une variable locale lorsque le compilateur est capable d'inférer (= deviner) le type
Par exemple, remplacer :
MaClasseAvecUnNomPasPossible maClasseAvecUnNomPasPossible = new MaClasseAvecUnNomPasPossible();
String message = "Hello World";
par :
var maClasseAvecUnNomPasPossible = new MaClasseAvecUnNomPasPossible();
var message = "Hello World";
Exercice : Déclaration, affectation

Instructions conditionnelles

if / else

if(a == null){
    a = 10;
}
else{
    b = 10;
}
Attention aux accolades :
int i = 2, j = 2, k = 3;
if(i == j)
    if(i == k)
        System.out.println("i égal k");
else
    System.out.println("i différent de j"); // PAS BON
(voir If.java)

En général, on conseille de toujours mettre des accolades et de respecter l'indentation.
Dans certains cas, plus lisible de ne pas respecter l'indentation :
if(n == 1){
    ...
}
else if(n == 2){
    ...
}
else if(n == 3){
    ...
}
else{
    ...
}
            
if(n == 1){
    ...
}
else{
    if(n == 2){
        ...
    }
    else{
        if(n == 3){
        ...
        }
        else{
            ...
        }
    }
}
            

switch (instruction)

Une instruction switch permet d'exprimer de manière plus claire une suite de if .. else if.
Le code précédent est équivalent à :
switch(n){
    case 1:
        ...
    break;
    case 2:
        ...
    break;
    case 3:
        ...
    break;
    default:
        ...
    break;
}
return ou throw permet aussi de sortir du switch
n peut être de type byte, short, char, int, enumerated type, String, Character, Byte, Short et Integer

switch (expression)

Depuis java 14, le mot-clé switch peut aussi être utilisé comme une expression (avec une valeur, contrairement à l'utilisation traditionnelle).
TODO A intégrer au cours
Voir cette page en attendant.

Boucles

while

while(expression)
    statement
        
int count = 0;
while(count < 10){
    System.out.println("count = " + count);
    count++;
}
        

do

Peu utilisé (on doit être dans une situation où on est sûrs de vouloir au moins une fois exécuter l'intérieur de la boucle).
do
    statement
while (expression)
        
int count = 0;
do {
    System.out.println("count = " + count);
    count++;
} while(count < 10);
        

for

Par exemple :
for(int i=0; i < 10; i++){
    System.out.println("i = " + i);
}
Syntaxe générale :
for(initialize; test; update)
    statement;
        
Equivalent à :
initialize;
while(test) {
    statement;
    update;
}
        
Possible de faire plusieurs initialisations ou update en les séparant par des virgules :
for(int i=0, j=10; i < 10; i++, j--){
    System.out.println("i = " + i + ", j = " + j);
}
les parties initialize, test, update sont optionnelles (mais les points-virgules sont obligatoire).
for(;;) fait donc une boucle infinie.
Exercice : Boucles

foreach

Permet d'itérer sur une collection sans la lourdeur des indexes :
for( declaration : expression )
    statement
Par exemple, on peut écrire :
int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
for(int n : primes)
    System.out.println(n); // et les accolades !!!
au lieu de :
for(int i = 0; i < primes.length; i++) {
    System.out.print(primes[i]);
}
La syntaxe for(int n : primes) signifie "for each n in primes".

Pratique mais ne peut pas remplacer complètement les boucles lorsqu'on veut parcourir de la fin vers le début, ou lorsque l'index est nécessaire :
for(int i = 0; i < words.length; i++) {
    if (i > 0) System.out.print(", "); // et les accolades !!!
    System.out.print(words[i]);
}

break

break permet de sortir de l'instruction qui la contient.
for(int i = 0; i < data.length; i++) {
    if (data[i] == target) {
        index = i;
        break;
    }
} // l'interpreteur arrive ici après le break
break peut être suivi par le label d'un labeled statement
iloop: for(int i=0; i < 10; i++){
    jloop: for(int j=0; j < 10; j++){
        break iloop;
    }
}// java arrive ici après le break

continue

Passe à l'itération suivante de la boucle
for(int i = 0; i < data.length; i++) {
    if(data[i] == false){
        continue;
    }
    process(data[i]);
}
Utilisé sans label, continue passe à l'itération suivante de la boucle la plus intérieure.
Utilisé avec un label (ex : continue iloop;), passe à l'itération suivante de la boucle labelisée.

return

Indique à java de stopper l'exécution de la méthode courante.
Si la méthode a été déclarée avec un type de retour, return doit être suivi par une expression (dont la valeur est du même type que celui déclaré dans la méthode).
double square(double x) {
    return x * x;
}
Si la méthode est déclarée void, return ne doit pas être suivi d'une expression.
void printSquareRoot(double x) {
    if (x < 0){
        return;
    }
    System.out.println(Math.sqrt(x));
}
Exercice : Année bissextile
Exercice Multiples1
Exercice TableMultiplication
Exercice Fibonacci
Exercice Calcul de pi

Autres instructions

java fournit d'autres instructions qu'on ne verra pas dans ce cours : synchronized, assert