3. La Font obtenue est de taille 1. Ce qui n'est pas terrible. On utilise alors deriveFont pour en créer des dérivées. Par exemple, on obtient du arial narrow grassouillet avec :
Font arialNarrowBold = arialNarrow.deriveFont(Font.BOLD);
Si besoin, créer une version avec ITALIC... Maintenant avec nos polices de bases, il suffit de modifier la taille.
Depuis Java SE 6, il est possible de placer des icônes dans le "system tray" du bureau. Toutefois cette fonctionnalité étant fortement dépendante du système d'exploitation et de son bureau, elle n'est pas forcément supporté dans tous les cas.
Voici un exemple de code permettant d'ajouter une icône dans le "systray" :
La classe abstraite Preferences du package java.util.prefs permet d'enregistrer facilement certaines données. Les implémentations dépendent du système (registres, SGBD, fichiers, etc.), mais ceci est invisible pour l'utilisateur. Plusieurs niveaux de préférences peuvent être définis. Ceux-ci sont accessibles grâce aux méthodes statiques de la classe Preferences.
Niveaux de Preferences
Preferences.systemRoot() : préférences globales du système.
Preferences.systemNodeForPackage(Class c) : préférences du système pour une classe donnée.
Preferences.userRoot() : préférences globales de l'utilisateur.
Preferences.userNodeForPackage(Class c) : préférences globales de l'utilisateur pour une classe donnée.
Les Preferences associent à une clef (String) une valeur. On peut stocker dans les Preferences tout type primitif. Voici les méthodes correspondantes à :
Pour un exemple plus complet, regardez le fichier ci-dessous. Il consiste en une fenêtre capable de "mémoriser" sa position, sa taille et son état (maximisée, iconifiée, etc.).
De nombreux formats de fichiers textes peuvent être utilisés avec Java. Malheureusement, tous ne sont pas utilisables grâce à l'API standard. Voici quelques formats et les API qui peuvent les utiliser :
Format
API
Remarques
HTML
API Standard
Cf. HTMLEditorKit, JEditorPane et JTextPane
RTF
API Standard
Cf. RTFEditorKit, JEditorPane et JTextPane
XML
API Standard (SAX, DOM)
Il existe de nombreuses librairies facilitant le travail avec ce type de documents
PDF
iText, FOP
Ces deux librairies sont utilisées seulement pour la génération
Cas de figure : Un composant donné a besoin d'informer un ou plusieurs composants externes que certains événements se sont produits.
Un petit exemple : Le composant MonBouton a besoin d'informer une liste d'objets que le bouton gauche de la souris est appuyé et que le bouton de la souris a été relaché.
En Java les types de base sont passés aux méthodes par valeur, ils ne peuvent pas être modifiés. Il va falloir utiliser une instance de classe "encapsulant" un entier pour effectuer la modification. Attention la classe fournie Integer ne permet pas la modification et est donc inutile dans ce cas.
Exemple :
class MonEntier {/**champprivé:*/privateint value;
/**miseàjourdelavaleur*/publicvoidsetValue(int newValue) {
value = newValue;
}/**Accesàlavaleur*/publicintgetValue() {return( value);
}}
Contrôle-C, kill -2, kill -15 et autres sont des méthodes permettant d'arrêter l'exécution du programme sans lui demander son avis. Pourtant, il est parfois nécessaire de faire au moins quelques instructions capitales telles que la fermeture de flux de fichier, d'une connexion réseaux ou une sauvegarde rapide. Pour se faire il est possible de définir un 'ShutdownHook' ce qui se traduit par un thread exécuté à la réception du signal de fin d'exécution.
Attention :
Ce traitement final sera également éxécuté lors de l'arrêt normal de l'application.
Cette astuce ne marche malheureusement pas pour le code kill -9.
Le garbage Collector (rammasse miette) est la partie de la JVM qui s'occupe de la récupération des zones mémoires. Les phases de libération de la mémoire sont gérées de façon autonome, il est inutile de lui dire quand passer.
Il existe néanmoins la fonction gc de la classe java.lang.System qui permet de faire un appel explicite au garbage collector. Cet appel ne garantit pas que tous les objets inutiles seront supprimés de la mémoire.
System.gc ();
//AppelexpliciteauGarbagecollector
Néanmoins, cette méthode ne garantit par le passage du GC. Elle permet seulement d'augmenter la propabilité que le GC se déclenche. Il est préférable de mieux gérer ses objets plutôt que d'utiliser cette méthode.
On peut ensuite utiliser MonEnum.Val1, MonEnum.Val2 etc, ou mieux on peut faire une interface plutôt qu'une classe afin que les classes l'implémentant puissent directement appeler Val1, Val2...
Le problème reste le typage des éléments, rien n'empêche de passer un objet d'une énumération à la place d'une autre.
2ème solution :
Cette deuxième catégorie de solutions, plus complexes, permet d'éviter ce problème. On peut par exemple penser à utiliser des instances de classes :
Ainsi un éventuel objet de type MonEnum2 ne pourra jamais être utilisé à la place d'un objet de type Enum1 (Enum1.Val1 Enum1.Val2 ...).
2ème bis solution :
La 2ème solution pose un autre problème, il reste possible d'utiliser la valeur 'null' alors que celle-ci n'appartient pas au domaine de notre énumération. Pour ce faire, rien de plus simple, si on considère val1 comme la valeur par défaut, on la déclare de la manière suivante :
publicfinalstatic Enum1 val1 =null;
De cette manière il devient totalement impossible de passé un Enum1 non reconnu !
3ème solution :
Depuis Java 5.0, vous pouvez utiliser le mot-clé enum pour créer des énumérations. Voilà comment vous pouvez procéder pour créer un énumération :
enum Align{
LEFT,
RIGHT,
CENTER,
JUSTIFIED
};
Vous pouvez accéder aux valeurs ainsi :
Align.LEFT;
Align.RIGHT;
Conclusion :
Si vous avez Java 5.0 ou une version plus récente, il vous faut utiliser la solution 3.
Si les paramètres passés au constructeur sont incohérents ou que la construction provoque une erreur, la création de l'objet est impossible et doit être annulée. Comme les méthodes, les constructeurs peuvent lever des exceptions, c'est la façon la plus simple d'annuler l'exécution du constructeur. Techniquement, l'objet est créer en mémoire (l'allocation de la mémoire a lieu au moment de l'appel du constructeur) mais l'appelant étant obligé de traiter l'exception, le pointeur vers cette objet est perdu et sera donc recupéré par le ramasse-miettes.
Une solution plus élégante consiste à utiliser par une méthode statique qui verifie les paramètres et ne fait l'appel au constructeur que si ceux-ci sont corrects.
publicclass Test {/**Leconstructeurestdéclaréprotected*pourinterdiresonutilisationparlesclientsdelaclasse.*/protectedTest(int val) {}publicstatic Test createTest( int val ) {if( val<100 ) {return( newTest(val) );
}else{return( null);
/**Oualorsonlèveuneexception...*/}}}
Class c = Class.forName("com.developpez.MaClasse");
On utilise le ClassLoader du système. Ce ClassLoader cache l'information et ne rechargera pas la classe lors d'un nouvel appel a forName, et ce même si le fichier ".class" a changé depuis le dernier chargement.
Il y a deux solutions :
1 - La classe n'est pas présente dans le CLASSPATH
C'est le cas le plus simple, il suffit d'utiliser un nouveau URLClassLoader a chaque fois pour recharger une nouvelle version de la classe.
//lecheminoutrouverlaclassearecharger
URL chemins[] ={newURL("file:/C:/MesClasses/") };
URLClassLoader loader =newURLClassLoader(chemins);
Class c = loader.loadClass("com.developpez.MaClasse");
Attention, il faut créer a chaque fois un nouvel URLClassLoader, sinon la classe est cachée par le loader.
Si la classe est présente dans le CLASSPATH c'est le ClassLoader du système qui la charge (et la met dans son cache).
2 - La classe est présente dans le CLASSPATH
Dans ce cas, on ne peut pas utiliser l'URLClassLoader. Il faut créer son propre ClassLoader qui hérite de la classe ClassLoader
import java.io.InputStream;
publicclass MonLoader extends ClassLoader
{public Class loadNewClass(String aName) throws Exception
{
InputStream is =getClass().getResourceAsStream("/"+ aName);
if (null== is)
{returnnull;
}byte buffer[] =newbyte[is.available()];
is.read(buffer);
Class c =defineClass(aName, buffer, 0, buffer.length);
resolveClass(c);
return c;
}}
Dans ce code, nous avons créé une nouvelle méthode "loadNewClass" qui recharge le fichier ".class" a chaque fois.
Le fait de ne pas avoir redéfini la méthode loadClass permet a ce nouveau ClassLoader d'utiliser le ClassLoader du système pour charger d'eventuelles classes mères ou interfaces.
Si la méthode loadClass est redéfinie il faut qu'elle puisse aussi trouver les super classes et interfaces des classes qui seront chargées, et ce sans recharger celles déjà présentes dans le système.
Un singleton est un objet qui ne peut être instancié qu'une seule et unique fois dans le programme.
Pour transformer une classe en singleton, il faut passer les constructeurs en accès private et ajouter au code une instance et un accesseur statiques :
Attention, toutes ces méthodes sont uniquement adaptées à un environnement mono-thread. Dans le cas où vous avez plusieurs processus, dirigez-vous vers cet article.
Le double-check locking est un idiome de programmation censé assurer la sécurité du Singleton en environnement multithread.
Attention, ce pattern ne marche pas !
Il existe trois solutions pour sécuriser un Singleton dans un programme multithread :
- Synchroniser toute la méthode getInstance() et payer le coût de la synchronisation à chaque appel
- Utiliser ThreadLocal dont l'implémentation n'est pas plus performante que la synchronisation
- Abandonner la synchronisation et utiliser un initialiseur static quand la construction du Singleton ne nécessite pas de paramètres particuliers.
La méthode toString() de la classe Object affiche le nom de l'objet suivi de la réference de l'objet dans la machine virtuelle (une sorte de pointeur).
Ceci peut s'avérer utile parfois lors d'une phase de deboguage, mais comment faire lorsqu'une classe (comme la classe String par exemple) redéfinie la méthode toString() ?
Il faut passer par la méthode System.identityHashCode(Object).
Le code suivant retourne la même chose que le toString() d'Object pour n'importe quel type d'objet:
public String getReference(Object anObject){return anObject.getClass().getName() +"@"+ Integer.toHexString(System.identityHashCode(anObject));
}
En utilisant la méthode statique currentTimeMillis() de la classe System. Cette méthode renvoie un long représentant la différence de temps entre le 1er Janvier 1970 à minuit et le temps système à l'exécution de l'instruction. Cette différence s'exprime en fonction de l'unité de temps de l'OS (la milliseconde, voire quelques dizaines de milliseconde).
En utilisant la méthode statique nanoTime() de la classe System. Cette méthode n'est disponible que depuis le JDK 5.0. Elle renvoie un long représentant la valeur du timer en nanosecondes.
Ce temps n'est pas lié à un référentiel de temps universel, aussi on ne doit-on l'employer que pour mesurer une différence de temps. La précision est de l'ordre de la nanoseconde.
Il faut aussi noter que ce n'est pas exactement le temps utilisée par votre application, mais le temps qui s'est écoulée depuis le début à la fin de votre code. Pendant ce temps, d'autres applications tournent sur votre ordinateur et utilisent ce qu'on appelle du temps CPU. Pour connaître exactement le temps CPU nécessaire, il faut utiliser la méthode getCurrentThreadCpuTime() de l'interface java.lang.management.ThreadMXBean à la place de System.nanoTime() ou currentTimeMillis() et vous aurez le temps CPU.
Avec Java 5.0, on peut utiliser la classe MemoryMXBean de la nouvelle API de management pour obtenir des informations sur l'état de la mémoire. On récupère une instance de MemoryMXBean via le code suivant :
Ensuite, il suffit d'utiliser la méthode getHeapMemoryUsage() qui retournera un objet MemoryUsage contenant le détail de la mémoire utilisé par votre application. Il contient ainsi les informations suivantes :
init : La taille initiale de la mémoire utilisé par l'application (généralement 0, sauf si l'on a utilisé l'option -Xms de la JVM).
used : La quantité de mémoire qui est réellement utilisée par votre application.
committed : La quantité de mémoire qui a été réservée auprès du système d'exploitation.
max : La quantité maximale que la JVM est authorisée à réserver auprès du système d'exploitation (modifiable avec l'option -Xmx de la JVM). Si l'application utilise plus de mémoire cela générera une OutOfMemoryError.
L'objet MemoryUsage possède bien entendu toutes les méthodes accésseurs pour accéder à ces éléments, mais également une redéfinition de la méthode toString() afin de les afficher plus simplement. Ainsi le code suivant :
init = 0(0K) used = 436912(426K) committed = 2031616(1984K) max = 530907136(518464K)
A noter également qu'il est possible de surveiller la mémoire utilisé pour la JVM elle-même (et non pas par votre application) via la méthode getNonHeapMemoryUsage().
Pour les versions précédentes à Java 5.0, on peut utiliser la classe Runtime et ses méthodes maxMemory(), totalMemory() et freeMemory() :
maxMemory() représente la quantité maximale que la JVM est authorisée à réserver auprès du système d'exploitation (équivalent de l'attribut max).
totalMemory() représente la quantité de mémoire qui a été réservée auprès du système d'exploitation (équivalent de l'attribut committed).
freeMemory() représente la quantité de mémoire libre utilisable avant que la JVM n'alloue encore de la mémoire auprès du système (équivalent à la différence entre les attributs committed et used)
Ainsi pour obtenir un résultat similaire, on peut utiliser le code suivant :
La méthode equals() permet d'indiquer qu'un objet est égal à un autre. L'implémentation par défaut hérité de Object se contente de renvoyer true lorsqu'il s'agit du même objet en mémoire et ne vérifie pas les valeurs des attributs de la classe (donc x.equals(y) équivaut à x==y).
Dès lors que l'on a besoin de tester l'égalité des instances d'une classe, il faut que cette dernière redéfinissent la méthode equals(). C'est également une condition pour le bon fonctionnement de certaines méthodes des Collections de Java.
La méthode equals() doit donc renvoyer true lorsque deux objets sont identiques, et false dans le cas inverse. Enfin il faut noter que cette méthode ne devraient pas renvoyer d'exception, même si son paramètre vaut null.
En général la méthode equals() se décompose en trois étapes :
Vérification de l'égalité des références : il est inutile de comparer les valeurs si les références sont identiques.
Vérification du type du paramètre : on ne peut pas comparer n'importe quel type.
Vérification des valeurs des attributs utiles des deux objets (c'est à dire des attributs représentatifs de la valeur de l'objet).
Il faut prendre en compte les règles suivantes lors de la redéfinition de la méthode equals() :
Réflection : x.equals(x) devrait toujours retourner true.
Symétrie : Si x.equals(y) retourne true, alors y.equals(x) retournera true.
Transitivité : Si x.equals(y) retourne true et y.equals(z) retourne true, alors x.equals(z) retourne true.
Consistance : Pour deux référence x et y, tous les appels à la méthode x.equals(y) devront toujours donner le même résultat.
Toutefois ces règles peuvent être difficilement réalisable en cas d'héritage lorsque de nouveaux attributs sont pris en compte.
Enfin, la librairie Jakarta Common Lang propose une classe EqualsBuilder qui facilite et simplifie l'écriture des méthodes equals() en se chargeant de la comparaison des attributs, ce qui pourrait donner dans ce cas :
Enfin, il faut noter qu'il est conseillé de redéfinir également la méthode hashCode() afin de respecter le contrat de cette dernière (qui est fortement lié à equals()).
La méthode hashCode() a pour objectif de fournir un code de hashage, afin d'optimiser le traitements des collections de type Map, qui se basent sur ce hashCode pour classer les données. Il est ainsi nécessaire de redéfinir la méthode hashCode() dès que l'on souhaire utiliser un objet en tant que clef d'une Map.
Mais plus généralement, il est souhaitable de redéfinir la méthode hashCode() lorsque l'on redéfinit la méthode equals(), afin de conserver une certaine cohérence.
En effet, ces deux méthodes sont très liées, puisque les hashCode de deux objets égaux doivent être égaux, ainsi l'on doit vérifier la condition suivante :
Si x.equals(y), alors x.hashCode() == y.hashCode()
Par contre l'inverse n'est pas forcément vrai. Ainsi deux objets différents peuvent avoir le même hashCode(). Toutefois ceci est à éviter dans la mesure du possible, car cela peut détériorer les performances des Map.
Mais le calcul du hashCode() doit rester raisonnables et peu complexe, afin d'éviter des temps de calcul trop important.
La solution les plus courantes est de calculer un hashCode() selon la valeurs des attributs utilisés dans la méthode equals() afin de conserver la cohérence entre les deux méthodes.
Pour le calcul du hashCode(), on peut raisonnablement suivre la règle suivante :
On choisit deux nombres impairs différents de zéro. Un de ces nombres servira comme valeur de départ pour le calcul du hashCode. Le second servira de "multiplieur" à chaque "ajout" du hashCode d'un attribut. Il est préférable d'utiliser des nombres premiers par sécurité.
En effet, si à chaque clé x , correspond h(x) l'endroit où se trouve x dans la table de hachage. L'expression de la fonction de Hachage est :
h(x)=[x(1)*B^(l-1) + x(2)*B^(l-2)....+x(l)] mod N
Prenons B = 128, et prenons une taille N égale a B, on obtient après calcul :
h(x)=x(l) mod N
car B mod N serait égale a 0
Une seule valeur a donc été rangée dans la table, voilà pourquoi il est très important de limiter au maximum les risques de diviseur commun car certaines valeurs disparaîtraient alors des tables.
NB : le choix de B comme d'une puissance de deux au lieu d'un nombre premier peut s'avérer judicieux pour des raisons de vitesse d'exécution. En effet une multiplication par deux est un simple décalage d'un bit à gauche pour l'ordinateur. Malgré tout, cela augmente les risques de diviseur commun et donc de disparition de donnée.
Calcul du hashCode de chaque attribut utile (ceux utilisés dans la méthode equals()), ce qui revient à faire (soit 'a' l'attribut) :
Pour un boolean, renvoyer 0 ou 1
(a ? 0 : 1)
Pour type entier (byte, char, short ou int) il suffit de prendre la valeur entière (avec un cast éventuel) :
(int)a;
Pour un type long, le convertir en int en déplacant les bits :
(int) (a^(a>>>32))
Pour le type float, on le convertit en int avec la méthode Float.floatToIntBits(a).
Pour le type double, on le convertit en long avec la méthode Double.doubleToLongBits(a), puis on effectue le même traitement que pour les long.
Pour les objets, on se contentera d'utiliser la méthode hashCode(), ou d'utiliser zéro si la référence est null :
(object==null ? 0, object.hashCode())
Pour un tableau, on traitera tous les éléments de ce dernier en respectant les règles ci dessus.
Et enfin on ajoute chacun des hashCodes calculés au résultat, après l'avoir multiplié par le nombre "multiplieur".
Ce qui pourrait donner (pour la classe en exemple dans la question précédente) :
publicinthashCode() {//Onchoisitlesdeuxnombresimpairsint result =7;
finalint multiplier =17;
//Pourchaqueattribut,oncalculelehashcode//quel'onajouteaurésultataprèsl'avoirmultiplié//parlenombre"multiplieur":
result = multiplier*result + attribut1;
result = multiplier*result + (attribut2==null ? 0 : attribut2.hashCode());
//Onretournelerésultat:return result;
}
Les nombres 7 et 17 ont été choisit de manière totalement arbitraire. Il est juste préférable de ne pas utiliser les mêmes pour toutes les classes.
Enfin, la librairie Jakarta Common Lang propose également une classe HashCodeBuilder qui facilite et simplifie l'écriture des méthodes hashCode() en se chargeant du calcul des hashCode des attributs selon leurs types, ce qui pourrait donner dans ce cas :
Il faut consommer tous les flux dans des threads différents.
La JVM utilise deux (petit) buffer pour lire la sortie stdout et stderr du programme appellé, mais lorsque ces buffers sont plein, le programme appellé reste bloqué sur la méthode d'écriture tant que le buffer de la JVM n'est pas vidé.
Voici un exemple complet :
Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec("monappli");
//Consommationdelasortiestandarddel'applicationexternedansunThreadseparenewThread() {publicvoidrun() {try{
BufferedReader reader =newBufferedReader(newInputStreamReader(process.getInputStream()));
String line ="";
try{while((line = reader.readLine()) !=null) {//Traitementdufluxdesortiedel'applicationsibesoinest}}finally{
reader.close();
}}catch(IOException ioe) {
ioe.printStackTrace();
}}}.start();
//Consommationdelasortied'erreurdel'applicationexternedansunThreadseparenewThread() {publicvoidrun() {try{
BufferedReader reader =newBufferedReader(newInputStreamReader(process.getErrorStream()));
String line ="";
try{while((line = reader.readLine()) !=null) {//Traitementdufluxd'erreurdel'applicationsibesoinest}}finally{
reader.close();
}}catch(IOException ioe) {
ioe.printStackTrace();
}}}.start();
Pour définir les commentaires javadoc au niveau du package, il faut créer un fichier HTML nommé "package.html" dans le répertoire du package. Ce fichier sera lu par l'outil javadoc pour générer la page de documentation du package, et son contenu (entre les balises <body> et </body>) sera alors utilisé comme commentaire pour le package, par exemple :
<HTML><BODY>
Documentation du package <b>com.masociete.monpack</b>.
@since 1.0
</BODY></HTML>
A partir de Java 5.0, le fichier "package.html" peut être remplacé par un fichier source java spécial nommé "package-info.java". Ce fichier ne doit contenir que la ligne de déclaration du package, avec ses commentaires "javadoc" et surtout ses éventuelles annotations. Par exemple :
La liste de toutes les options pour les versions 1.3.1 à 1.6.0 de la Java HotSpot VM (plateforme SPARC/Solaris) sont disponibles sur le site de Sun Microsystems : A Collection of JVM Options
D'autres éléments relatifs aux options de la JVM sont disponibles aux adresses suivantes: