LA VERSION DE MA JVM ET LES CLASSPATH

cs_danimo Messages postés 16 Date d'inscription mercredi 18 juillet 2007 Statut Membre Dernière intervention 15 mars 2009 - 28 sept. 2007 à 08:16
verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019 - 13 oct. 2007 à 06:06
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/44196-la-version-de-ma-jvm-et-les-classpath

verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019
13 oct. 2007 à 06:06
Note: HashTable<K,V> a été modifié dans Java6 pour implémenter AUSSI l'interface Map<K,V>. Cela ne change pas l'arbre d'héritage (simple) de HashTable. Mais cela permet justement d'écrire la boite noire nécessaire. Du coup, HashTable n'est pas obsolète, au contraire de Dictionnary qui n'a pas subit le même traitemet (on se demande pourquoi ce qui était acceptable pour HashTable ne l'a pas été pour Dictionnary... sans doûte de sérieux problèmes de compatibilité du code de la librairie qui doit être capable de différencier une instance de Dictionnary d'une instance de Map par l'opération "instanceOf", et des problèmes de compatibilité lors de la désérialisation de flux sérialisés par une version précédente de Java, puisque cela modifie l'ordre de reconstruction des instances par les différents constructeurs d'objets de chaque type). Si on peut critiquer quelquechose dans la façon où la classe System a été étendue, est l'oubli de typage fort pour System.getProperties() qui se contente de retourner une "Enumeration<?>" alors qu'évidemment cela aurait dû être "Enumeration<String>" (quitte à ce que cela rende certaines implémentations de la ClassPath pour la JVM incompatible). Cela suggère que toutes les valeurs de clés retournées par System.getProperties ne sont pas forcément de type String (au contraire de ce que "dit" la classe "Properties"): c'est le cas en fait lors de l'initialisation de la JVM (quand la classe System est initialisée), même si plus tard dans nos programmes il ne reste que des clés de type String qui sont les seules accessibles par l'API documentée.

En revanche la class Properties a été étendue d'une autre façon: elle supporte la nouvelle méthode stringPropertyNames() qui retourne une "Enumeration<String>" sans qu'il soit nécessaire d'utiliser un typecast d'erasure (de la capture de ?) en Object (implicitement valide) ou String (quidoit êtrevérifié à l'exécution)

Aussi on peut écrire en Java6 bien plus proprement (plus aucun typecast):

java.util.Enumeration<String> cles =
System.getProperties().stringPropertyNames(); // pas de typecast ici!
while (cles.hasMoreElements()) {
String cle = cles.nextElement(); // pas de typecast ici !
System.out.println( cle + " = " + System.getProperty(cle) );
}

Cela répond au problème de performance (pas de vérification à l'exécution) et de sécurité du typage fort (vérification complète à la compilation). Mais ce n'est pas encore très pratique. On aimerait bien pouvoir simplifier cette syntaxe sansavoir à utiliser de variables d'états intermédiaires (source d'erreurs ou d'effets de bord dans les programmes).

Un exemple de classe miroir cachant le typecast effectué sur l'erasure des types "T<?>" en types "T", ici pour T=Enumeration:

class IterableEnumeration<T> implements java.util.Iterator<T> {
private final java.util.Enumeration<T> e;
public IterableEnumeration(java.util.Enumeration<T> e) {
this.e = e;
}
public boolean hasNext() {
return this.e.hasMoreElements();
}
public T next() {
return this.e.nextElement();
}
public void remove() {
throw new UnsupportedOperationException("Iterator's on IterableEnumeration's are not removable");
}
}

On peut alors étendre Properties de cette façon (fonctionne aussi en Java 1.5) :

class IterableProperties extends java.util.Properties implements java.lang.Iterable{
public IterableProperties(java.util.Properties p) {
super(p);
}
public IterableEnumeration<String> iterator() {
return new IterableEnumeration<String>((java.util.Enumeration<String>)this.propertyNames()); // warning sur l'erasure...
}
}
Noter que ci-dessus l'erasure a été isolée dans le typecast de la méthode iterator, pour lequel il est simple de vérifier que l'ensemble retourné par propertyNames() ne contient que des String et non n'importe quel type d'objet.

On peut encore faire mieux en Java6 (pour Java 1.5 la version précédente sera nécessaire) pour éviter ce warning, en employant la méthode stringPropertyNames pour lequel cette assomption est totalement évitée:

class IterableProperties extends java.util.Properties implements java.lang.Iterable {
public IterableProperties(java.util.Properties p) {
super(p);
}
public IterableEnumeration<String> iterator() {
return new IterableEnumeration<String>((java.util.Enumeration<String>)this.stringPropertyNames());
}
}

Et ensuite on peut écrire de façon totalement sûre (grace au typage fort):

for (String cle : new IterableProperties(System.getProperties()).iterator())
System.out.println(cle + " = " + System.getProperty(cle))

C'est exactement ce qu'on voulait: plus de variable intermédiaire, pas même pour l'itérateur, plus moyen d'échapper au contrôle de "scope", et un programme totalement vérifiable (car totalement dépourvu de variable d'état ayant des effets de bord possibles), performant (plus de contrôle de type à l'exécution, ceci étant totalement vérifié par le compilateur).
verdy_p Messages postés 202 Date d'inscription vendredi 27 janvier 2006 Statut Membre Dernière intervention 29 janvier 2019
13 oct. 2007 à 04:29
Variante sans utiliser aucun typecast (donc sans vérification de type à l'exécution pour chaque clé, mais directment à la compilation), pour Java 6:

for (java.util.Map.Entry<String, Object> e : System.getProperties().entrySet())
System.out.println(e.getkey() + " = " + e.getValue());

Ladifférence ici est queentrySet est hérité de HashTable mais ne contient pasla liste deséléments hérités pour les valeurs par défaut (normalement il n'y a pas d'héritage de valeurs par défaut pour les propriétés de System).

Mais on peut alors vouloir utiliser propertyNames() pour avoir aussi les valeurs héritées, et le source précédent devient (en Java 6):

for (String cle : System.getProperties().propertyNames())
System.out.println( cle + " = " + System.getProperty(cle) );

ce qui serait finalement bien mieux (cela fait appel en interne au polymorphisme à typage fort de propertyNames() qui en Java 6 retourne une valeur de type polymorphe "Enumeration<?>", et non de type plus spécialisé "Enumeration" (monomorphe) qui en est dérivé (<?> y prend la valeur <Object> et ce type dérivé demandera donc un typecast "(String)", verifié à l'exécution pour convertir une instance de type "Object" en "String" pour revenir au type parent polymorphe et vérifier que l'Object est convertible en String, ce qui n'est possible que si les éléments dans la liste de propriété supportent l'opération toString().

L'autre solution serait de sous-typer explicitement l'Enumeration retournée par propertyNames(), ce qui ne nécessiterait qu'un seul typecast, hors de la boucle:

java.util.Enumeration<String> cles = System.getProperties().propertyNames();
for (String cle : cles)
System.out.println( cle + " = " + System.getProperty(cle) );

ou encore de façon équivalente, sans déclaration de variable temporaire:

for (String cle : (java.util.Enumeration<String>)System.getProperties().propertyNames())
System.out.println( cle + " = " + System.getProperty(cle) );

Malheureusement, cela ne marche toujours pas car le type "Enumeration<?>" n'implémente pas l'interface "Iterable" nécessaire pour utiliser la boucle for simplifiée (ceci pour des raisons historiques de compatibilité ascendante, le type Enumeration n'ayant pas été conçu initialement comme implémentant cette interface, il n'est donc pas compatible)...

En attendant on peut toujours écrire (sans typecast dans la boucle):

java.util.Enumeration<String> cles =
(java.util.Enumeration<String>)System.getProperties().propertyNames();
while (cles.hasMoreElements()) {
String cle = cles.nextElement(); // pas de typecast ici !
System.out.println( cle + " = " + System.getProperty(cle) );
}

On n'a plus de typechecking dans la boucle mais un typechecking dans l'expression affectée à l'initialisation de la variable "cles". Ce typechecking est résiduel et signalé comme efectuant un typechecking sur l'erasure "Enumeration" et non "Enumeration<?>", c'est valide mais peu sûr.

Alors comment éviter le typecast à chaque boucle et des warning partout? L'idée est de créer un type (une classe en Java) dérivé de "Enumeration<?>" et implémentant l'interface "java.util.Iterable". Pas évident, car "Properties<K,V>" hérite de "HashTable<K,V>" qui n'est pas une Collection mais hérite de la classe "Dictionnary<K,V>" (lui même dérivé directement de Object et non de l'interface "Map<K,V>"). Là encore la raison est historique...

La doc de Java a beau dire que java.util.Dictionnary est obsolète, on ne peut s'en passer ni pour HashTable, ni pour Properties et donc pas non plus pour la classe System qui devraient toutes devenir obsolètes pour être remplacées par de nouvelles classes se basant sur "Collection".

Voilà quelques manques dans l'API du JDK pour rendre le typage plus fort dans Java 6, et permettre de bannir totalement le typecast dans un projet complexe.

Il y a des projets qui l'exigent, et activent une option du compilateur signalant TOUS les typecasts (et les "erasures" de types polymorphes en types simples) et les recencent systématiquement, même ceux qui sont à priori autorisés par le langage, car la vérification à l'exécution est source d'ennuis imprévisibles et complique sérieusement la vérification du code: dans des cas comme ça on est obligé d'encapsuler cette opération dans une "boîte noire" et former des batteries de tests assez conséquentes, avec de nombreuses assertions à vérifier, et des tonnes de commentaires pour le justifier.

Pour des cas comme ça, on cherche à isoler le problème et on construit des classes "miroirs" comme Map par rapport à Dictionnary, mais aussi les miroirs de HashTable, Properties et System, et on marque ces anciennes classes comme obsolètes et à remplacer par leur "miroir" plus sécurisés et agissant comme boîtes noires testées isolément.
yvkoe Messages postés 32 Date d'inscription jeudi 20 septembre 2007 Statut Membre Dernière intervention 19 janvier 2009
12 oct. 2007 à 11:21
Bravo Celphys2 ca c'est cool.
Citer ses sources et ne rien s'attribuer est tellement rare.
Chapeau bas, monseigneur.
GillesWebmaster Messages postés 496 Date d'inscription mercredi 30 juin 2004 Statut Membre Dernière intervention 29 juillet 2009 1
1 oct. 2007 à 17:14
celphys2, belle honnêteté intellectuelle!
celphys2 Messages postés 8 Date d'inscription vendredi 24 mars 2006 Statut Membre Dernière intervention 3 mars 2009
1 oct. 2007 à 09:35
En effet ta source est pratique. Cependant si vous souhaitez obtenir la liste complète des propriétés 'System' vous pouvez utiliser :

java.util.Enumeration liste = System.getProperties().propertyNames();
String cle;
while( liste.hasMoreElements() ) {
cle = (String)liste.nextElement();
System.out.println( cle + " = " + System.getProperty(cle) );
}

Cette source est extraite de la FAQ de developpez.com que vous trouvez sur : http://java.developpez.com/faq/java/?page=systeme#SYSTEME_variable_environnement

L'auteur en est Clément Cunin. Je ne pense pas qu'il m'en voudra de la promouvoir.
GillesWebmaster Messages postés 496 Date d'inscription mercredi 30 juin 2004 Statut Membre Dernière intervention 29 juillet 2009 1
1 oct. 2007 à 07:02
Classe!
Simple et clair!
cs_danimo Messages postés 16 Date d'inscription mercredi 18 juillet 2007 Statut Membre Dernière intervention 15 mars 2009
28 sept. 2007 à 08:16
C'est simple, c'est clair et instructif.
Rejoignez-nous