P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ ROSES ARE #FF0000, VIOLETS ARE #0000FF, ALL MY BASE ARE BELONG TO YOU Articles | Connexion
 
~Les mot-clés louches de Java

Précédent  
  Java  
  Suivant
 Présentation

Le langage Java contient peu de mot-clés. On en dénombre un peu moins de cinquantes. Ceci est très peu comparé à d'autres langages tels que le Visual Basic (plus de 100) ou le C# (environ 75). Pourtant, parmi les mot-clés classiques, les for, if et consort, se trouvent trois mot-clés plus exotiques, dont l'utilisation n'est que très rarement enseignée aux débutants.
Nos trois compères se nomment strictfp, transient et volatile.
 Sommaire


 strictfp

Sans doute le plus méconnu des mot-clés de Java. Ce mot clé, qui est une abréviation de Strict floating point, s'applique en tant que modificateur d'accès. Ou plus simplement, on l'utilise de la même manière que les mot-clés public ou synchrnonized. Avec quelques restrictions.

Utilisation

strictfp s'applique en tant que modificateurs de classes, de méthodes ou d'interfaces. L'entité affectée est alors dite "FP-strict".

// classe "FP-strict"
public strictfp class FP
{
}

// interface "FP-strict"
protected strictfp interface FPInterface
{
}

public class FPTest
{
  // méthode "FP-strict"
  private strictfp foo()
  {
  }
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Une des caractéristiques de ce mot-clé est qu'il est délégué à la descendance de l'entité impliquée. Prenons pour exemple le code source suivant:

// classes publique, FP-strict
public strictfp class FPDelegate
{
  // méthode friendly, FP-strict
  void bar()
  {
  }
}
       
      
JextCopier dans Jext
Dans le code précédent, le fait que la classe FPDelegate soit FP-strict implique que sa méthode bar le soit également. A l'inverse le fait que la classe soit public n'impose pas que sa méthode le soit !

Mais strictfp ne peut pas s'appliquer aux méthodes d'interfaces, ni aux constructeurs de classes (à l'inverse de public par exemple):

// code interdit
protected interface FooBar
{
  strictfp doFooBar();
}

// code interdit
public class Bar
{
  public strictfp Bar()
  {
  }
}
       
      
JextCopier dans Jext
Effet

Qu'apporte exactement l'utilisation de strictfp ? Comme son nom l'indique, strictfp agit sur les opérations en virgule flottante. C'est à dire sur les types primitifs double et float. Voici une opération en virgule flottante:

// classe FP-strict
public strictfp class FPDemo
{
  public static void main(String[] args)
  {
    double d = 8e+307;
    // affiche 4 * d * 0.5 donc 2 * d
    System.out.println(4.0 * d * 0.5);
    // affiche 2 * d
    System.out.println(2 * d);
  }
}
       
      
JextCopier dans Jext
Ce programme écrit dans la console les calculs 4 * 8e+307 * 0.5 et 2 * 8e+307, ce qui, normalement, devrait produire le même résultat. Pourtant la première ligne affiche Infinity et la seconde 1.6e+308. Le type double possède pour limite supérieur 1.8e+308.

Or Java effectue les calculs en garantissant une priorité de la gauche vers la droite. Le calcul 4 * d * 0.5 est interprété ainsi: (4 * d) * 0.5. Il s'avère que le résultat de 4 * d est égal à 3.2e+308. Ce résultat est supérieur à la limite du type double. Java interprète donc ce résultat comme l'infini. Ensuite, et à cause du mot-clé strictfp, l'application du calcul * 0.5 ne change rien, puisque nous multiplions par l'infini. En effet, ce mot-clé impose à la JVM d'évaluer la suite du calcul sans rien changer.

Notons que le mot-clé oblige l'implémentation de la JVM a évaluer l'ensemble de l'expression. Ne pas faire usage de ce mot-clé ne garantit pas que la JVM réalisera ce calcul de la sorte. Une JVM peut en effet avoir le droit, si la méthode n'est pas FP-strict, d'effectuer le calcul intermédiaire 4 * d dans un type capable de contenir le résultat avant de faire la suite du calcul. Ainsi le calcul 4 * d * 0.5 pourra, sur une JVM donnée, afficher le même résultat que 2 * d.

En conclusion, nous avons vu que le mot-clé strictfp permettait de garantir les mêmes calculs, quelle que soit la machine virtuelle sur laquelle l'opération est effectuée.


 transient

Le mot-clé transient est lié à la sérialisation des classes Java. Mais avant de nous étendre sur le sujet, rappelons en quelques mots ce qu'est la sérialisation.

Java, tout comme de nombreux langages, permet de sauvegarder l'état d'un objet à un instant donné. Cette sauvegarde se fait généralement dans simple fichier texte. L'intérêt est majeur. Imaginer que vous programmiez un jeu et que vous possédiez une classe Player. Pour sauvegarder la partie vous pourrez sérialiser une instance de la classe Player dans un fichier. Lorsque le joueur décidera de reprendre sa partie, vous serez en mesure de recréer une instance de Player telle qu'elle était lors de la sauvegarde.

// flux de sortie
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

import java.io.IOException;

public class Writer
{
  Writer()
  {
    try
    {
      // on écrit un objet dans un fichier
      writeObject();
      // on charge un objet depuis un fichier
      readObject();
    } catch (Exception ioe) {
      // une erreur est survenue
      System.err.println("Erreur: " + ioe.toString());
    }
  }

  private void readObject() throws Exception
  {
    // on lit l'objet depuis le fichier writeable.ser
    FileInputStream file = new FileInputStream("writeable.ser");
	ObjectInputStream in = new ObjectInputStream(file);

    Writeable w;
    // on crée l'objet à partir du fichier
    w = (Writeable) in.readObject();

    // on affiche les valeurs:
    System.out.println("demo: " + w.demo + " - ghost: " + w.ghost);

    // fermeture
    file.close();
  }

  private void writeObject() throws IOException
  {
    // on écrit l'objet dans le fichier writeable.ser
    FileOutputStream file = new FileOutputStream("writeable.ser");
	ObjectOutputStream out = new ObjectOutputStream(file);

    // on crée un objet
    Writeable w = new Writeable();
    // on écrit l'objet dans le fichier
    out.writeObject(w);

    // on affiche les valeurs:
    System.out.println("demo: " + w.demo + " - ghost: " + w.ghost);

    // fermeture
    out.flush();
    file.close();
  }

  public static void main(String[] args)
  {
    // démarrage
    new Writer();
  }
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Le code précédent va créer un objet Writeable et le sérialiser dans un fichier. Ensuite, nous allons créer une nouvelle instance de Writeable à partir du fichier nouvellement créé. Voici le code de l'objet Writeable:

// la classe que nous allons sérialiser
class Writeable implements java.io.Serializable
{
  // deux entiers
  public int demo = 4;
  public int ghost = 19;
}
       
      
JextCopier dans Jext
Ici l'objet ne propose aucun élément transient. L'exécution du programme créera le fichier writeable.ser et affichera les deux lignes suivantes:
demo: 4 - ghost: 19
demo: 4 - ghost: 19
La première ligne correspond aux valeurs par défaut lorsque l'on crée, instancie l'objet, via new. La second ligne correspond aux valeurs attribuées lors de la création de l'objet depuis le fichier de sérialisation.

Transformons la classe Writeable pour lui faire utiliser le mot-clé transient:

// la classe que nous allons sérialiser
class Writeable implements java.io.Serializable
{
  // entier transient
  public transient int demo = 4;
  // entier normal
  public int ghost = 19;
}
       
      
JextCopier dans Jext
En exécutant de nouveau le programme, nous voyons apparaître les lignes:
demo: 4 - ghost: 19
demo: 0 - ghost: 19
Dans le second cas, demo vaut 0 ! Pourtant, le code spécifie bien que la valeur par défaut de demo est 4. Eh oui, mais l'attribution d'une valeur par défaut se fait lors de l'instanciation de l'objet ! Or, la méthode consistant à lire un objet depuis un fichier ne crée pas cet instance explicitement. Donc demo n'est jamais initialisé avec sa valeur par défaut. De plus, comme cet attribut est transient, il n'est pas écrit dans le fichier (comparez la taille des deux fichiers). Cela implique que demo ne reçoit aucune valeur et contient donc 0.

Ce mot-clé trouve des applications dès lors qu'une donnée sensible ne doit en aucun cas apparaître dans un fichier. Un mot de passe par exemple. Mais ce mot-clé peut également permettre de "remettre à zéro" certaines valeurs. Dans le cas d'un jeu, on pourra ainsi ne pas sauvegarder le temps de jeu depuis le début de la partie.


 volatile

Toute application un tant soit peu importante fait appel aux threads. Les threads permettent de simuler l'exécution simultanée de plusieurs tâches. Le processeur répartit le temps d'exécution entre les divers threads. Ceux-ci s'exécutent petit bout par petit bout, les uns à la suite des autres. On a alors l'illusion d'une exécution en parallèle (l'illusion devient réalité dans le cas de machines possédant plusieurs processeurs). Le mot-clé volatile intervient dans le cadre des threads en s'appliquant aux variables.

Les machines virtuelles Java possèdent deux emplacements dans lesquelles elles placent les valeurs en cours d'exécution: le tas et la pile. Les valeurs placées dans la pile sont accessibles beaucoup plus rapidement car les machines virtuelles sont optimisées dans cette optique. Or, lesdites machines virtuelles effectuent souvent des optimisations consistant à déplacer des valeurs du tas vers la pile. Pourtant de telles optimisations ont parfois de gros désavantages. Considérons le code source suivant:

public boolean doLoop = true;
//  ...
while (doLoop);
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Ce code, à déconseiller (:-))), entre dans une boucle infinie et n'en sors que lorsque la variable doLoop vaut false. Or si vous tentez de modifier la valeur de cette variable depuis un autre thread, cela ne marchera pas. Par exemple, vous avez une classe contenant cette boucle. Cette même classe crée une fenêtre avant de lancer la boucle. Vous concevez votre application de telle sort qu'en appuyant sur un bouton, la boucle se termine. Malheureusement, les fenêtres possèdent leur propre thread d'exécution...

Lorsque votre bouton exécutera le code maclass.doLoop = false, rien ne changera. La JVM aura en effet déplacé la valeur de doLoop dans une pile, inaccessible depuis un autre thread. Lorsque votre bouton sera cliqué, la valeur de doLoop changera... mais dans le tas et non dans la pile ! Afin d'éviter cet écueil vous devez déclarer votre variable volatile.

public volatile boolean doLoop = true;
//  ...
while (doLoop);
       
      
JextCopier dans Jext
Et maintenant votre bouton pourra modifier la variable. Le mot clé volatile interdit à la JVM d'optimiser la variable en la déplacant dans la pile. La valeur n'étant donc jamais déplacée dans la pile, le code exécuté par le bouton modifiera la valeur dans le tas, celle qui sera aussi utilisée par la boucle while.

Note: insistons encore sur le fait que cet exemple est un très mauvais exemple de programmation. Si vous tenez à tout prix à réaliser une telle boucle d'attente, ce que nous vous déconseillons fortement, écrivez-là ainsi:

public volatile boolean doLoop = true;
//  ...
while (doLoop)
{
  Thread.yield();
}
       
      
JextCopier dans Jext
La ligne supplémentaire Thread.yield() met en attente tous les threads en activité et permet aux autres de s'exécuter. Cette ligne évite de monopoliser toutes les ressources système.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006


Précédent  
  Java  
  Suivant

 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE