1.1. Java 2 , les outils élémentaires



Plan de ce chapitre:   ...........retour au plan général

1. Interprétation et compilation de java

    1.1 Bytecode et Compilation native
    1.2 La machine virtuelle Java
    1.3 JIT , Hotspot


2. Les éléments de base en Java

    2.1 Tous les types élémentaires, transtypage
    2.2 Variables, valeurs, constantes
    2.3 Priorité d'opérateurs


3. Les opérateurs en Java

    3.1 Opérateurs arithmétiques
    3.2 Opérateurs de comparaison
    3.3 Opérateurs booléens
    3.4 Opérateurs bit level
     

1. Interprétation et compilation de java
 
 

    Rappelons qu'un ordinateur ne sait exécuter que des programmes écrits en instructions machines compréhensibles par son processeur central. Java comme pascal, C etc... fait partie de la famille des langages évolués (ou langages de haut niveau) qui ne sont pas compréhensibles immédiatement par le processeur de l'ordinateur. Il est donc nécesaire d'effectuer une "traduction" d'un programme écrit en langage évolué afin que le processeur puisse l'exécuter.

    Les deux voies utilisées pour exécuter un programme évolué sont la compilation ou l'interprétation :
     
    Un compilateur du langage X pour un processeur P, est un logiciel qui traduit un programme source écrit en X en un programme cible écrit en instructions machines exécutables par le processeur P.

     
    Un interpréteur du langage X pour le processeur P, est un logiciel qui ne produit pas de programme cible mais qui effectue lui-même immédiatement les opérations spécifiées par le programme source.

    Un compromis assurant la portabilité d'un langage : une pseudo-machine
     
    Lorsque le processeur P n'est pas une machine qui existe physiquement mais un logiciel simulant (ou interprétant) une machine on appelle cette machine pseudo-machine ou p-machine. Le programme source est alors traduit par le compilateur en instructions de la pseudo-machine et se dénomme pseudo-code. La p-machine standard peut ainsi être implantée dans n'importe quel ordinateur physique à travers un logiciel qui simule son comportement; un tel logiciel est appelé interpréteur de la p-machine.

    La première p-machine d'un langage évolué a été construite pour le langage pascal assurant ainsi une large diffusion de ce langage et de sa version UCSD dans la mesure où le seul effort d'implementation pour un ordinateur donné était d'écrire l'interpréteur de p-machine pascal, le reste de l'environnement de développement (éditeurs, compilateurs,...) étant écrit en pascal était fourni et fonctionnait dès que la p-machine était opérationnelle sur la plate-forme cible.
     
    Donc dans le cas d'une p-machine le programme source est compilé, mais le programme cible est exécuté par l'interpréteur de la p-machine.

    Beaucoup de langages possèdent pour une plate-forme fixée des interpréteurs ou des compilateurs, moins possèdent une p-machine, Java est l'un de ces langages. Nous décrivons ci-dessous le mode opératoire en Java.
     
     

    1.1 Bytecode et Compilation native

    Compilation native

    La compilation native consiste en la traduction du source java (éventuellement préalablement traduit instantanément en code intermédiare) en langage binaire exécutable sur la plate-forme concernée. Ce genre de compilation est équivalent à n'importe quelle compilation d'un langage dépendant de la plate-forme, l'avantage est la rapidité d'exécution des instructions machines par le processeur central.


    Programe source java : xxx.java (portable)
    Programe exécutable sous windows : xxx.exe (non portable)
     
     

    Bytecode

    La compilation en bytecode (ou pseudo-code ou p-code ou code intermédiaire) est semblable à l'idée du p-code de N.Wirth pour obtenir un portage multi plate-formes du pascal. Le compilateur Javac traduit le programme source xxx.java en un code intermédiaire indépendant de toute machine physique et non exécutable directement, le fichier obtenu se dénomme xxx.class. Seule une p-machine (dénommée machine virtuelle java) est capable d'exécuter ce bytecode. Le bytecode est aussi dénommé instructions virtuelles java.
     

    Figure : un programme source Exemple.java est traduit par le compilateur (dénommé Javac ) en un programme cible écrit en bytecode nommé Exemple.class


    Exemple.java ------------> compilateur Javac ------> Exemple.class


    1.2 La machine virtuelle Java

    Une fois le programme source java traduit en bytecode, la machine virtuelle java se charge de l'exécuter sur la machine physique à travers son système d'exploitation (Windows, Unix, MacOs,...)

    Inutile d'acheter une machine virtuelle java, tous les navigateurs internet modernes (en tout cas Internet explorer et Netscape) intègrent dans leur environnement une machine virtuelle java qui est donc installée sur votre machine physique et adaptée à votre système d'exploitation, dès que votre navigateur internet est opérationnel.
     

    Fonctionnement élémentaire de la machine virtuelle Java

    Une machine virtuelle Java contient 6 parties principales
     


    Comme toute machine la machine virtuelle Java est fondée sur l'architecture de Von Neumann et elle exécute les instructions séquentiellement un à une.
     

    Figure : un synoptique de la machine virtuelle Java

    Les registres sont des mémoires 32 bits :

 

2. Les éléments de base  en Java
 

Tout n'est pas objet dans Java, par souci de simplicité et d'efficacité, Java est un langage fortement typé. Comme en Delphi, en Java vous devez déclarer un objet ou une variable avec son type avant de l'utiliser. Java dispose de même de type prédéfinis ou type élémentaires.
 
 
 

2.1 Tous les type élémentaires de Java et le transtypage

les types servent à déterminetr la nature du contenu d'une variable, du résultat d'une opération, d'un retour de résultat de fonction.
 
type élémentaire intervalle de variation nombre de bits
boolean false , true 1 bit
byte -128 ... +127 8 bits
char caractères unicode (valeurs de 0 à 65536) 16 bits
double Virgule flottante double précision ~5.10^308 64 bits
float Virgule flottante simple précision ~9.10^18 32 bits
int entier signé -2^31-1... +2^31 32 bits
long entier signé long -2^63-1... +2^63 64 bits
short entier signé court -2^15-1... +2^15 16 bits

Rappellons qu'en java toute variable qui sert de conteneur à une valeur d'un type élémentaire précis doit préalablement avoir été déclarée sous ce type.

Il est possible d'indiquer au compilateur le type d'une valeur numérique en utilisant un suffixe :
 
  • l ou L pour désigner un entier du type long
  • f ou F pour désigner un réel du type float
  • d ou D pour désigner un réel du type double.

Exemples :
45 ou 45L représente la valeur 45 en entier signé sur 64 bits.
45f  ou 45F représente la valeur 45 en virgule flottante simple précision sur 32 bits.
45d  ou 45D représente la valeur 45 en virgule flottante double précision sur 64 bits.
5.27e-2 ou 5.27e-2F représente la valeur 0.0527 en virgule flottante simple précision sur 32 bits.

Transtypage opérateur (  )

Les conversions de type en Java sont identiques pour les types numériques aux conversions utilisées dans un langage fortement typé comme Delphi par exemple (pas de conversion implicite). Si vous voulez malgré tout, convertir une valeur immédiate ou une valeur contenue dans une variable il faut explicitement transtyper cette valeur à l'aide de l'opérateur de transtypage noté: ( ).
 


 

2.2 Variables, valeurs, constantes en Java
 
Identificateurs de variables
Déclarations et affectation de variables
Les constantes en Java
Base de représentation des entiers

Comme en Delphi, une variable Java peut contenir soit une valeur d'un type élémentaire, soit une référence à un objet. Les variables jouent le même rôle que dans les langages de programmation classiques impératifs, leur visibilité est étudié dans le prochain chapitre.

Les identificateurs de variables en Java se décrivent comme ceux de tous les langages de programmation :

Identificateur Java :

Attention Java fait une différence entre majuscules et minuscules, c'est à dire que la variable BonJour n'est pas la même que la variable bonjour ou encore la variable Bonjour. En plus des lettres, les caractères suivants sont autorisés pour construire une identificateur Java : "$" , "_" , "µ" et les lettres accentuées.

Exemples de déclaration de variables :

int Bonjour ;  int µEnumération_fin$;
float Valeur ;
char UnCar ;
boolean Test ;

etc ...

Exemples d'affectation de valeurs à ces variables :
Affectation Déclaration avec initialisation
Bonjour = 2587 ;
Valeur = -123.5687
UnCar =  'K' ;
Test = false ;
int Bonjour = 2587 ;
float Valeur = -123.5687
char UnCar =  'K' ;
boolean Test = false ;

Exemple avec transtypage :
int Valeur ;
char car = '8' ;
Valeur = (int)car - (int)'0';
 
fonctionnement de l'exemple :
Lorsque la variable car est l'un des caractères '0', '1', ... ,'9', la variable Valeur est égale à la valeur numérique associée (il s'agit d'une conversion car = '0' ---> Valeur = 0, car = '1' ---> Valeur = 1, ... , car = '9' ---> Valeur = 9).
 

Les constantes en Java ressemblent à celles du pascal

Ce sont des variables dont le contenu ne peut pas être modifié, elles sont précédées du mot clef final :

  final int x=10 ; x est déclarée comme constante entière initialisée à 10.
  x = 32 ;  <------ provoquera une erreur de compilation interdisant la modification de la valeur de x.
 

Base de représentation des entiers

Java peut représenter les entiers dans 3 bases de numération différentes  : décimale (base 10), octale (base 8), hexadécimale (base 16). La détermination de la base de représentation d'une valeur est d'ordre syntaxique grâce à un préfixe :
 
  • pas de préfixe  ----> base = 10  décimal.
  • préfixe 0          ----> base = 8    octal
  • préfixe 0x        ----> base = 16   hexadécimal

 
 

2.3 Priorité d'opérateurs en Java

Les 39 opérateurs de Java sont détaillés par famille, plus loin . Ils sont utilisés comme dans tous les langages impératifs pour manipuler, séparer, comparer ou stocker des valeurs. Les opérateurs ont soit une seul opérande, soit deux opérandes, il n'existe en Java qu'un seul opérateur à trois opérandes (comme en C) l'opérateur conditionnel  " ? : ".

Dans le tableau ci-dessous les opérateurs de Java sont classés par ordre de priorité croissante (0 est le plus haut niveau, 14 le plus bas niveau). Ceci sert lorsqu'une expression contient plusieurs opérateurs à indiquer l'ordre dans lequel s'effectueront les opérations.
 


 
priorité opérateurs
0  ( )     [  ]      .
1  !     ~     ++    -- 
2  *     /    %
3  +    -
4  <<    >>    >>>
5  <     <=    >   >=
6  = =     !=
 &
8  ^
9  |
10  &&
11  ||
12  ? :
13  =  *=  /=  %=  +=  -=  ^=  &=  <<=  >>=  >>>=  |=

 
 
 
 

3. Les opérateurs en Java 

    Opérateurs arithmétiques
    Opérateurs de comparaison
    Opérateurs booléens
    Opérateurs bit level

Les opérateurs d'affectation seront mentionnés plus loin comme cas particulier de l'instruction d'affectation.
   

3.1 Opérateurs arithmétiques

opérateurs travaillant avec des opérandes à valeur immédiate ou variable
Opérateur priorité action exemples
 + 1 signe positif  +a;  +(a-b); +7 (unaire)
 - 1 signe négatif  -a;  -(a-b); -7 (unaire)
 * 2 multiplication 5*4;  12.7*(-8.31); 5*2.6
 / 2 division 5 / 2;    5.0 / 2;   5.0 / 2.0
 % 2 reste 5 % 2;    5.0 %2;   5.0 % 2.0
 + 3 addition a+b; -8.53 + 10; 2+3
 - 3 soustraction a-b; -8.53 - 10; 2-3

Ces opérateurs sont binaires (à deux opérandes) exceptés les opérateurs de signe positif ou négatif. Ils travaillent tous avec des opérandes de types entiers ou réels. Le résultat de l'opération est converti automatiquement en valeur du type des opérandes.

L'opérateur " % " de reste n'est intéressant que pour des calculs sur les entiers longs, courts, signés ou non signés : il renvoie le reste de la division euclidienne de 2 entiers.

Exemples d'utilisation de l'opérateur de division selon les types des opérandes et du résultat :
 
programme Java résultat obtenu  commentaire
   int x = 5 , y  ;   x = 5 , y =???   déclaration
   float a , b = 5 ;   b = 5 , a =???   déclaration
   y = x / 2 ;    y = 2  // type int   int x et int 2
 résultat : int
   y = b / 2 ;   erreur de conversion conversion automatique
impossible (float b --> int y)
   y = b / 2.0 ;   erreur de conversion conversion automatique
impossible (float b --> int y)
   a = b / 2 ;    a = 2.5  // type float  float b et int 2
 résultat : float
   a = x / 2 ;    a = 2.0  // type float  int x et int 2
 résultat : int
conversion automatique
int 2 --> float  2.0
  a = x / 2f ;   a = 2.5  // type float  int x et float 2f
 résultat : float

Pour l'instruction précédente " y = b / 2 " engendrant une erreur de conversion voici deux corrections possibles utilisant le transtypage :

y = (int)b / 2 ;    // b est converti en int avant la division qui s'effectue sur deux int.
y = (int)(b / 2) ; // c'est le résultat de la division qui est converti en int.
 

opérateurs travaillant avec une  unique variable comme opérande
Opérateur priorité action exemples
 ++ 1 post ou pré incrémentation :
incrémente de 1 son opérande numérique : short, int, long, char, float, double.
++a;  a++; (unaire)
 -- 1 post ou pré décrémentation :
décrémente de 1 son opérande numérique : short, int, long, char, float, double.
--a;  a--; (unaire)

L'objectif de ces opérateurs est l'optimisation de la vitesse d'exécution du bytecode dans la machine virtuelle Java.

post-incrémentation : k++
la valeur de k est d'abord utilisée telle quelle dans l'instruction, puis elle est augmentée de un à la fin. Etudiez bien les exemples ci-après qui vont vous permettre de bien comprendre le fonctionnement de cet opérateur.

Nous avons mis à côté de l'instruction Java les résultats des contenus des variables après exécution de l'instruction de déclaration et de la post incrémentation.

Exemple 1 :
 int k = 5 , n ;
 n = k++ ;
  n = 5   k = 6

Exemple 2 :
  int k = 5 , n ;
  n = k++ - k ;
  n = -1   k = 6
Dans l'instruction k++ - k nous avons le calcul suivant : la valeur de k (k=5) est utilisée comme premier opérande de la soustraction, puis elle est incrémentée (k=6), la nouvelle valeur de k est maintenant utilisée comme second opérande de la soustraction ce qui revient à calculer n = 5-6 et donne n = -1 et k = 6.

Exemple 3 :
  int k = 5 , n ;
  n = k - k++  ;
  n = 0   k = 6
Dans l'instruction k - k++ nous avons le calcul suivant : la valeur de k (k=5) est utilisée comme premier opérande de la soustraction, le second opérande de la soustraction est k++ c'est la valeur actuelle de k qui est utilisée (k=5) avant incrémentation de k, ce qui revient à calculer n = 5-5 et donne n = 0 et k = 6.

Exemple 4 :Utilisation de l'opérateur de post-incrémentation en combinaison avec un autre opérateur unaire.
int nbr1, z , t , u , v  ;
  nbr1 = 10 ;
  v = nbr1++ 
  v = 10   nbr1 = 11
  nbr1 = 10 ;
  z = ~ nbr1 ;
  z = -11    nbr1 = 10
  nbr1 = 10 ;
  t = ~ nbr1 ++ ;
  t = -11   nbr1 = 11
  nbr1 = 10 ;
  u = ~ (nbr1 ++) ;
  u = -11   nbr1 = 11

  La notation " (~ nbr1) ++ " est refusée par Java.
remarquons que les expressions "~nbr1 ++  " et  "~ (nbr1 ++)" produisent les mêmes effets, ce qui est logique puisque lorsque deux opérateurs (ici ~ et ++ )ont la même priorité, l'évaluation a lieu de gauche à droite.
 

pré-incrémentation : ++k
la valeur de k est d'abord augmentée de un ensuite utilisée dans l'instruction.

Exemple1 :
int k = 5 , n ;
  n = ++k ;   n = 6   k = 6

Exemple 2 :
  int k = 5 , n ;
  n = ++k - k ;
  n = 0   k = 6
Dans l'instruction ++k - k nous avons le calcul suivant : le premier opérande de la soustraction étant ++k c'est donc la valeur incrémentée de k (k=6) qui est utilisée, cette même valeur sert de second opérande à la soustraction ce qui revient à calculer n = 6-6 et donne n = 0 et k = 6.

Exemple 3 :
  int k = 5 , n ;
  n = k - ++k  ;
  n = -1   k = 6
Dans l'instruction k - ++k nous avons le calcul suivant : le premier opérande de la soustraction est k (k=5), le second opérande de la soustraction est ++k, k est immédiatement incrémenté  (k=6) et c'est sa nouvelle valeur incrémentée qui est utilisée, ce qui revient à calculer n = 5-6 et donne n = -1 et k = 6.
 
 

post-décrémentation : k--
la valeur de k est d'abord utilisée telle quelle dans l'instruction, puis elle est diminuée de un à la fin.

Exemple1 :
int k = 5 , n ;
  n = k-- ;   n = 5   k = 4

 

pré-décrémentation : --k
la valeur de k est d'abord diminuée de un puis utilisée dans l'instruction.

Exemple1 :
int k = 5 , n ;
  n = --k ;   n = 4   k = 4

Reprenez avec l'opérateur - - des exemples semblables à ceux fournis pour l'opérateur ++ afin d'étudier le fonctionnement de cet opérateur (étudiez (- -k - k) et (k -  - -k)).
 
 

3.2 Opérateurs de comparaison

Ces opérateurs employés dans une expression renvoient un résultat de type booléen (false ou true). Nous en donnons la liste sans autre commentaire car ils sont strictement identiques à tous les opérateurs classiques de comparaison de n'importe quel langage algorithmique (C, pascal, etc...). Ce sont des opérateurs à deux opérandes.
 
Opérateur priorité action exemples
 < 5 strictement inférieur 5 < 2 ;  x+1 < 3 ;   y-2 < x*4
 <= 5 inférieur ou égal -5 <= 2 ;  x+1 <= 3 ;  etc...
 > 5 strictement supérieur 5 > 2 ;  x+1 > 3 ;  etc...
 >= 5 supérieur ou égal 5 >= 2 ;    etc...
 = = 6 égal 5 = = 2 ;  x+1 = = 3 ; etc...
 != 6 différent 5 != 2 ;  x+1 != 3 ; etc...

 
 

3.3 Opérateurs booléens

Ce sont les opérateurs classiques de l'algèbre de boole { { V, F }, ! , & , | } où { V, F } représente l'ensemble {Vrai,Faux}.

Les connecteurs logiques ont pour syntaxe en Java :  ! , & , | , ^  

: { V, F } x { V, F } ®{ V, F } (opérateur binaire qui se lit " et ")
|    : { V, F } x { V, F } ®{ V, F } (opérateur binaire qui se lit " ou ")
!    : { V, F } ¾®{ V, F } (opérateur unaire qui se lit " non ")
^    : { V, F } x { V, F } ®{ V, F } (opérateur binaire qui se lit " ou exclusif ")

Table de vérité des opérateurs ( p et q étant des expressions booléennes)
p q ! p p & q p | q  p ^ q
V V F V V F
V F F F V V
F V V F V V
F F V F F F

Remarque :
"p Î { V, F } , "q Î { V, F }  , p &q est toujours évalué en entier ( p et q sont toujours évalués).
"p Î { V, F } , "q Î { V, F }  , p |q  est toujours évalué en entier ( p et q sont toujours évalués).
Java dispose de 2 clones des opérateurs binaires & et | . Ce sont les opérateurs && et || qui se différentient de leurs originaux & et | par leur mode d'exécution optimisé (application de théorèmes de l'algèbre de boole) :
 

Opérateur  et optimisé : &&
Théorème
"q Î { V, F }  ,  F &q = F

Donc si p est faux (p = F)  , il est inutile d'évaluer q car l'expression p &q est fausse (p &q = F), comme l'opérateur & évalue toujours l'expression q, Java à des fins d'optimisation de la vitesse d'exécution du bytecode dans la machine virtuelle Java, propose un opérateur ou noté && qui a la même table de vérité que l'opérateur & mais qui applique ce théorème.
"p Î { V, F } , "qÎ { V, F }  , p &&q = p &q
Mais dans p&&q  ,  q n'est évalué que si p = V.

 

Opérateur ou optimisé : | |
Théorème
"q Î { V, F }  ,  V |q = V

Donc si p est vrai (p = V)  , il est inutile d'évaluer q car l'expression p |q est vraie (p |q = V), comme l'opérateur |  évalue toujours l'expression q, Java à des fins d'optimisation de la vitesse d'exécution du bytecode dans la machine virtuelle Java, propose un opérateur ou noté ||  qui applique ce théorème et qui a la même table de vérité que l'opérateur | .
"p Î { V, F } , "qÎ { V, F }  , p ||q = p |q
Mais dans p||q  ,  q n'est évalué que si p = F.

En résumé:
Opérateur priorité action exemples
 ! 1 non booléen  ! (5 < 2) ;  !(x+1 < 3) ; etc...
 & 7 et booléen complet (5 = = 2) & (x+1 < 3) ;   etc...
  | 9 ou booléen complet (5 != 2) | (x+1 >= 3) ;   etc...
 && 10 et booléen optimisé (5 = = 2) && (x+1 < 3) ;   etc...
 || 11 ou booléen optimisé (5 != 2) || (x+1 >= 3) ;   etc...

Nous allons voir ci-après une autre utilisation des opérateurs &et | sur des variables ou des valeurs immédiates en tant qu'opérateur bit-level.