Astuce Java 124: suivez vos étapes dans Java 1.4

Je ne sais pas pour vous, mais j'aime vraiment savoir où je suis. Étant un gars, je ne suis jamais perdu, mais parfois je ne sais pas où je suis. Certains endroits, tels que les centres commerciaux, ont des cartes avec des indicateurs «Vous êtes ici». De même, Java nous permet maintenant de déterminer notre emplacement avec l'aide du système. Dans cette astuce, je vais vous montrer comment extraire ces informations de localisation du système de manière cohérente et fiable.

Je crois fermement que le système d'exécution devrait fournir suffisamment de métadonnées sur le système lui-même pour que les programmes puissent prendre de meilleures décisions et effectuer des tâches. Java a été capable d'introspecter et de réfléchir sur les classes pendant un certain temps, mais jusqu'à présent, il lui manquait la capacité simple de mapper le code d'exécution à sa position dans le fichier de code source. La solution antérieure à Java 1.4 consistait à analyser manuellement la trace de pile d'une exception. Maintenant, avec Java 1.4, nous avons une meilleure solution.

Séparer une trace de pile?

La solution de contournement antérieure à Java 1.4 pour collecter les informations de localisation consistait à analyser manuellement la printStackTrace()sortie d' une exception . Voici un exemple de trace de pile simple:

java.lang.Throwable à boo.hoo.StackTrace.bar (StackTrace.java:223) à boo.hoo.StackTrace.foo (StackTrace.java:218) à boo.hoo.StackTrace.main (StackTrace.java:54) 

Séparer le code ci-dessus n'est pas un problème d'analyse majeur. Mais qu'en est-il de ça?

java.lang.Throwable à boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) à boo.hoo.StackTrace $ FirstNested. (StackTrace.java:256) à boo.hoo.StackTrace. (StackTrace.java : 246) sur boo.hoo.StackTrace.main (StackTrace.java:70) 

Pouah. Que signifie vraiment tout cet étrange goobley-guk, et pourquoi diable devrais-je l'analyser? De toute évidence, le système suit déjà ces informations de localisation, car il est capable de créer ces traces de pile. Alors pourquoi ces informations de localisation ne sont-elles pas disponibles directement? Eh bien, avec Java 1.4, c'est finalement le cas.

De plus, gardez à l'esprit que face aux compilateurs JIT (juste à temps) et aux compilateurs dynamiques et optimisés comme HotSpot de Sun Microsystems, les informations sur les numéros de fichiers et de lignes peuvent ne pas exister. L'objectif de «performance ou buste» peut certainement être gênant.

Java 1.4 Throwable à la rescousse!

Après avoir toléré des années de plaintes, Sun Microsystems a finalement étendu la java.lang.Throwableclasse avec la getStackTrace()méthode. getStackTrace()renvoie un tableau de StackTraceElements, où chaque StackTraceElementobjet fournit le moyen d'extraire plus ou moins directement les informations de localisation.

Pour acquérir ces informations de mappage, vous créez toujours une Throwableinstance au point d'intérêt de votre code:

// ... public static void main (String [] args) {Throwable ex = new Throwable (); // ...

Ce code positionne ce point au début de main().

Bien sûr, il est inutile de simplement rassembler ces informations sans en faire quelque chose. Pour cette astuce, nous utiliserons chaque méthode sous-jacente du StackTraceElements pour extraire et afficher toutes les informations possibles.

Le programme d'exemple,, StackTrace.javamontre comment extraire les informations de localisation avec plusieurs exemples. Vous aurez besoin du SDK J2SE (Java 2 Platform, Standard Edition) 1.4 pour compiler et exécuter le programme d'exemple.

Pour extraire et afficher les informations de mappage, l'exemple de code utilise une méthode d'assistance,, displayStackTraceInformation()avec l'idiome d'utilisation de base suivant:

// ... public void crashAndBurnout () {// ... displayStackTraceInformation (new Throwable ()); // ...} // ...

Le displayStackTraceInformation()code est assez simple:

public static boolean displayStackTraceInformation (Throwable ex, boolean displayAll) {if (null == ex) {System.out.println ("Null stack trace reference! Bailing ..."); retourner faux; } System.out.println ("La pile selon printStackTrace (): \ n"); ex.printStackTrace (); System.out.println (""); StackTraceElement [] stackElements = ex.getStackTrace (); if (displayAll) {System.out.println ("Le" + stackElements.length + "élément" + ((stackElements.length == 1)? "": "s") + "de la trace de pile: \ n" ); } else {System.out.println ("L'élément supérieur d'une trace de pile d'éléments" + stackElements.length + ": \ n"); } for (int lcv = 0; lcv <stackElements.length; lcv ++) {System.out.println ("Filename:" + stackElements [lcv] .getFileName ());System.out.println ("Numéro de ligne:" + stackElements [lcv] .getLineNumber ()); String className = stackElements [lcv] .getClassName (); Chaîne packageName = extractPackageName (className); String simpleClassName = extractSimpleClassName (className); System.out.println ("Nom du package:" + ("" .equals (packageName)? "[Package par défaut]": packageName)); System.out.println ("Nom complet de la classe:" + className); System.out.println ("Nom de classe simple:" + simpleClassName); System.out.println ("Nom de classe non fusionné:" + unmungeSimpleClassName (simpleClassName)); System.out.println ("Nom de classe directe:" + extractDirectClassName (simpleClassName)); System.out.println ("Nom de la méthode:" + stackElements [lcv] .getMethodName ()); System.out.println ("Méthode native?:"+ stackElements [lcv] .isNativeMethod ()); System.out.println ("toString ():" + stackElements [lcv] .toString ()); System.out.println (""); if (! displayAll) renvoie true; } System.out.println (""); retourne vrai; } // Fin de displayStackTraceInformation ().

Fondamentalement, nous appelons getStackTrace()le pass-in Throwable, puis effectuons une boucle sur les StackTraceElements individuels , en extrayant autant d'informations de mappage que possible.

Notez le peu de cruauté que le displayAllparamètre introduit. displayAlllaisse le site appelant décider d'afficher ou non tous les StackTraceElements ou uniquement l'élément le plus haut de la pile. L'exemple de programme utilise le displayAllparamètre pour limiter la sortie à une quantité raisonnable.

La plupart des informations de trace de pile sont directement utiles. Par exemple, le StackTraceElement.getMethodName()renvoie une chaîne contenant le nom de la méthode, tandis que StackTraceElement.getFileName()renvoie une chaîne avec le nom de fichier source d'origine. Lisez le StackTraceElementJavadoc pour la liste complète des méthodes.

Noms de classe à gogo!

Comme vous l'avez probablement remarqué, le displayStackTraceInformation()code utilise plusieurs méthodes d'assistance supplémentaires pour séparer la valeur renvoyée par StackTraceElement.getClassName(). Ces méthodes d'assistance sont nécessaires car elles StackTraceElement.getClassName()retournent le nom qualifié complet de la classe et StackTraceElementn'ont aucune autre méthode pour fournir les parties sous-jacentes du nom complet de la classe. Nous en apprendrons davantage sur chaque méthode d'assistance supplémentaire en parcourant les différents exemples d'utilisation de displayStackTraceInformation().

Paquets par défaut ou nommés

Étant donné le nom complet de la classe, extractPackageName()donne le nom du package dans lequel réside la classe:

public static String extractPackageName (String fullClassName) ("" .equals (fullClassName))) return ""; int lastDot = fullClassName.lastIndexOf ('.'); if (0> = lastDot) return ""; return fullClassName.substring (0, lastDot);  

Fondamentalement, extractPackageNameextrait tout ce qui précède le dernier point du nom de classe complet. Cette information précédente se trouve être juste le nom du package.

Remarque: vous pouvez commenter / décommenter l'instruction de package en haut de StackTrace.javapour explorer la différence entre l'exécution de l'exemple de programme dans le package par défaut sans nom et son exécution dans le boo.hoopackage. Par exemple, lorsqu'il n'est pas commenté, l'affichage de l'élément de pile le plus haut pour l'appel à bar()from foo()from main()devrait ressembler à ceci:

Nom de fichier: StackTrace.java Numéro de ligne: 227 Nom du package: boo.hoo Nom complet de classe: boo.hoo.StackTrace Nom de classe simple: StackTrace Nom de classe non fusionné: StackTrace Direct nom de classe: StackTrace Nom de méthode: bar Méthode native ?: false toString ( ): boo.hoo.StackTrace.bar (StackTrace.java:227) 

Sinon, si vous commentez l'instruction package, alors l'élément de pile ci-dessus devrait ressembler à ceci:

Nom de fichier: StackTrace.java Numéro de ligne: 227 Nom du package: [package par défaut] Nom complet de la classe: StackTrace Nom de la classe simple: StackTrace Nom de la classe Unmunged: StackTrace Direct nom de la classe: StackTrace Nom de la méthode: bar Méthode native ?: false toString (): StackTrace .bar (StackTrace.java:227) 

Les noms de classe peuvent-ils jamais être simples?

La prochaine méthode d'aide que nous utilisons est extractSimpleClassName(). Comme vous le verrez, les résultats de cette méthode ne sont pas nécessairement simples, mais je veux clairement distinguer ce nom de classe simplifié du nom de classe pleinement qualifié.

Fondamentalement, extractSimpleClassName()complète extractPackageName():

public static String extractSimpleClassName (String fullClassName) ("" .equals (fullClassName))) return ""; int lastDot = fullClassName.lastIndexOf ('.'); if (0> lastDot) return fullClassName; return fullClassName.substring (++ lastDot);  

En d'autres termes, extractSimpleClassName()renvoie tout ce qui suit le dernier point ( .) du nom de classe complet. Par exemple, à partir du même appel à bar()ci-dessus, nous voyons que le nom de classe simple est juste StackTrace, que le code fasse ou non partie du package par défaut ou d'un package nommé.

Nous obtenons des résultats plus intéressants lorsque nous tournons notre attention vers les classes imbriquées. Dans l'exemple de programme, j'ai créé deux niveaux de classes nommées imbriquées ( FirstNestedet FirstNested.SecondNested) avec une classe interne anonyme supplémentaire (à l'intérieur FirstNested.SecondNested).

Toute l'utilisation imbriquée commence par:

public StackTrace (booléen na) {StackTrace.FirstNested nested = new StackTrace.FirstNested (); }

Notez que le paramètre booléen ( na) ne signifie rien. Je viens de l'ajouter car d'autres constructeurs doivent être distingués.

Voici les classes imbriquées:

classe publique FirstNested {public FirstNested () {StackTrace.displayStackTraceInformation (new Throwable ()); StackTrace.FirstNested.SecondNested yan = new StackTrace.FirstNested.SecondNested (); System.out.println ("Dumping from inside hogwash ():"); yan.hogwash (); } classe publique SecondNested {public SecondNested () {StackTrace.displayStackTraceInformation (new Throwable ()); } public void hogwash () {StackTrace.displayStackTraceInformation (new Throwable ()); Whackable whacked = new Whackable () {public void whack () {StackTrace.displayStackTraceInformation (new Throwable ()); }}; // Fin de la classe de membre anonyme. whacked.whack (); } // Fin de hogwash (). } // Fin de la classe membre FirstNested.SecondNexted. } // Fin de la classe membre FirstNested.

L'élément de pile le plus haut pour SecondNestedle constructeur de s ressemble à ceci:

Nom de fichier: StackTrace.java Numéro de ligne: 267 Nom du package: boo.hoo Nom complet de classe: boo.hoo.StackTrace $ FirstNested $ SecondNested Nom de classe simple: StackTrace $ FirstNested $ SecondNested Nom de classe non fusionné: StackTrace.FirstNested.SecondNested Direct nom de classe: SecondNested Nom de la méthode: Méthode native?: False toString (): boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) 

Vous pouvez voir que le nom de classe simple n'est pas si simple dans ce cas. Les classes imbriquées se distinguent des classes imbriquées de niveau supérieur et de la classe de niveau supérieur en utilisant le caractère de signe dollar ( $). Donc, techniquement, le nom "simple" de la deuxième classe imbriquée est StackTrace$FirstNested$SecondNested.

J'ai fourni la unmungeSimpleClassName()méthode pour remplacer les signes dollar par des périodes d'exhaustivité.

Comme je suis têtu, je voulais toujours obtenir le nom de classe vraiment simple, alors j'ai créé extractDirectClassName():