Programmering

Java Tip 124: Spor dine trin i Java 1.4

Jeg ved ikke om dig, men jeg kan virkelig godt lide at vide, hvor jeg er. At være fyr er jeg aldrig tabt, men nogle gange ved jeg bare ikke, hvor jeg er. Nogle steder, såsom indkøbscentre, har kort med "Du er her" -indikatorer. På samme måde lader Java os nu finde ud af vores placering med systemets hjælp. I dette tip viser jeg dig, hvordan du udtrækker disse placeringsoplysninger fra systemet på en konsekvent og pålidelig måde.

Jeg er overbevist om, at runtime-systemet skal give nok metadata om selve systemet, så programmer kan tage bedre beslutninger og udføre opgaver. Java har været i stand til at introspektere og reflektere over klasser i nogen tid, men indtil nu har det manglet den enkle evne til at kortlægge runtime-kode tilbage til sin position i kildekodefilen. Pre-Java 1.4-løsningen var at manuelt analysere en undtagelsens stacksporing. Nu med Java 1.4 har vi en bedre løsning.

Trække et stakspor fra hinanden?

Løsningen til Java 1.4 til indsamling af placeringsoplysninger var at manuelt analysere en undtagelse printStackTrace () produktion. Her er et eksempel på et simpelt stakspor:

java.lang.Trowable at boo.hoo.StackTrace.bar (StackTrace.java:223) at boo.hoo.StackTrace.foo (StackTrace.java:218) at boo.hoo.StackTrace.main (StackTrace.java:54) 

At trække ovenstående kode fra hinanden er ikke et stort analyseproblem. Men hvad med dette?

java.lang.Trowable at boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) at boo.hoo.StackTrace $ FirstNested. (StackTrace.java:256) at boo.hoo.StackTrace. (StackTrace.java : 246) på boo.hoo.StackTrace.main (StackTrace.java:70) 

Ugh. Hvad betyder al den underlige goobley-guk egentlig, og hvorfor i alverden skal jeg analysere det? Det er klart, at systemet allerede sporer disse placeringsoplysninger, da det er i stand til at bygge disse stakspor. Så hvorfor er disse placeringsoplysninger ikke tilgængelige direkte? Nå, med Java 1.4 er det endelig.

Derudover skal du huske, at i lyset af JIT (just-in-time) kompilatorer og dynamiske optimerings kompilatorer som Sun Microsystems 'HotSpot, findes der muligvis ikke oplysninger om filer og linjenumre. Målet med "performance eller bust" kan bestemt være generende.

Java 1.4 Kan kastes til undsætning!

Efter at have tolereret mange års klager har Sun Microsystems endelig udvidet java.lang. kan kastes klasse med getStackTrace () metode. getStackTrace () returnerer en matrix af StackTraceElements, hvor hver StackTraceElement objekt tilvejebringer midlerne til mere eller mindre direkte at udtrække placeringsoplysninger.

For at erhverve disse kortoplysninger skal du stadig oprette en Kan kastes eksempel på det interessepunkt i din kode:

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

Denne kode placerer det punkt i starten af hoved ().

Selvfølgelig er det nytteløst at bare indsamle disse oplysninger uden at gøre noget med det. Til dette tip bruger vi hver underliggende metode til StackTraceElements for at udtrække og vise alle de oplysninger, vi kan.

Eksempelprogrammet, StackTrace.java, viser hvordan man udtrækker placeringsoplysningerne med flere eksempler. Du skal bruge J2SE (Java 2 Platform, Standard Edition) 1.4 SDK til at kompilere og køre eksempelprogrammet.

Eksempelkoden bruger en hjælpemetode til at udtrække og vise kortlægningsoplysningerne, displayStackTraceInformation (), med følgende grundlæggende brugsform:

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

Det displayStackTraceInformation () kode er ret ligetil:

 public static boolean displayStackTraceInformation (Throwable ex, boolean displayAll) {if (null == ex) {System.out.println ("Null stack track track reference! Bailing ..."); returner falsk; } System.out.println ("Stakken i henhold til printStackTrace (): \ n"); ex.printStackTrace (); System.out.println (""); StackTraceElement [] stackElements = ex.getStackTrace (); if (displayAll) {System.out.println ("" + stackElements.length + "element" + ((stackElements.length == 1)? "": "s") + "af stack-sporet: \ n" ); } andet {System.out.println ("Det øverste element i et" + stackElements.length + "element stack-spor: \ n"); } for (int lcv = 0; lcv <stackElements.length; lcv ++) {System.out.println ("Filnavn:" + stackElements [lcv] .getFileName ()); System.out.println ("Linjenummer:" + stackElements [lcv] .getLineNumber ()); String className = stackElements [lcv] .getClassName (); String packageName = extractPackageName (className); Streng simpleClassName = extractSimpleClassName (className); System.out.println ("Pakke navn:" + ("" .equals (pakkenavn)? "[Standardpakke]": pakkenavn)); System.out.println ("Fuld klasse navn:" + className); System.out.println ("Simpel klasse navn:" + simpleClassName); System.out.println ("Unmunged class name:" + unmungeSimpleClassName (simpleClassName)); System.out.println ("Direkte klassenavn:" + extractDirectClassName (simpleClassName)); System.out.println ("Metodenavn:" + stackElements [lcv] .getMethodName ()); System.out.println ("Native metode ?:" + stackElements [lcv] .isNativeMethod ()); System.out.println ("toString ():" + stackElements [lcv] .toString ()); System.out.println (""); hvis (! displayAll) returnerer true; } System.out.println (""); returner sandt; } // Slut på displayStackTraceInformation (). 

Dybest set kalder vi getStackTrace () på den indleverede Kan kastes, og løb derefter gennem individet StackTraceElements, udtrække så mange kortoplysninger som muligt.

Læg mærke til den smule af cruft den displayAlle parameter introducerer. displayAlle lader det kaldende websted beslutte, om alt skal vises StackTraceElements eller bare stakens øverste element. Eksempelprogrammet bruger displayAlle parameter for at begrænse output til et rimeligt beløb.

De fleste af stack-sporingsoplysningerne er direkte nyttige. F.eks StackTraceElement.getMethodName () returnerer en streng, der indeholder metodens navn, mens StackTraceElement.getFileName () returnerer en streng med det originale kildefilnavn. Læs StackTraceElement Javadoc for den komplette liste over metoder.

Klassenavne i massevis!

Som du sikkert har bemærket, er displayStackTraceInformation () kode bruger flere yderligere hjælpemetoder til at trække den værdi, der returneres af, fra hinanden StackTraceElement.getClassName (). Disse hjælpemetoder er nødvendige, fordi StackTraceElement.getClassName () returnerer klassens fuldt kvalificerede navn og StackTraceElement har ingen andre metoder til at give de underliggende dele af det fuldt kvalificerede klassenavn. Vi lærer om hver ekstra hjælpemetode ved at arbejde igennem de forskellige eksempler på anvendelser af displayStackTraceInformation ().

Standard vs. navngivne pakker

Givet det fuldt kvalificerede klassenavn, extractPackageName () giver navnet på den pakke, hvor klassen bor:

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

I bund og grund, extractPackageName udtrækker alt forud for den sidste prik i det fuldt kvalificerede klassenavn. De foregående oplysninger er tilfældigvis pakkens navn.

Bemærk: Du kan kommentere / fjerne kommentar fra pakkeerklæringen øverst på StackTrace.java for at undersøge forskellen mellem at køre eksempelprogrammet i standardpakken, der ikke er navngivet, i stedet for at køre den i boo.hoo pakke. For eksempel, når der ikke er kommenteret, vises visningen af ​​det øverste stakelement for opkaldet til bar() fra foo () fra hoved () skal se sådan ud:

Filnavn: StackTrace.java Linjenummer: 227 Pakkenavn: boo.hoo Fuldt klassenavn: boo.hoo.StackTrace Enkelt klassenavn: StackTrace Unmunged klassenavn: StackTrace Direkte klassenavn: StackTrace Metodenavn: bar Indfødt metode ?: falsk toString ( ): boo.hoo.StackTrace.bar (StackTrace.java:227) 

Alternativt, hvis du kommenterer pakkeerklæringen, skal ovenstående stakelement ligne dette:

Filnavn: StackTrace.java Linjenummer: 227 Pakkenavn: [standardpakke] Fuldt klasse navn: StackTrace Enkelt klassenavn: StackTrace Unmunged klasse navn: StackTrace Direkte klasse navn: StackTrace Metodenavn: bar Indfødt metode ?: falsk tilString (): StackTrace .bar (StackTrace.java:227) 

Kan klassenavne nogensinde være enkle?

Den næste hjælpemetode, vi bruger, er extractSimpleClassName (). Som du vil se, er metodens resultater ikke nødvendigvis enkle, men jeg vil klart skelne dette forenklede klassenavn fra det fuldt kvalificerede klassenavn.

I bund og grund, extractSimpleClassName () supplerer extractPackageName ():

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

Med andre ord, extractSimpleClassName () returnerer alt efter den sidste prik (.) fra det fuldt kvalificerede klassenavn. For eksempel fra det samme opkald til bar() ovenfor ser vi, at det enkle klassenavn er retfærdigt StackTrace, uanset om koden er en del af standardpakken eller en navngivet pakke.

Vi får mere interessante resultater, når vi vender vores opmærksomhed mod indlejrede klasser. I eksempelprogrammet oprettede jeg to niveauer af indlejrede, navngivne klasser (FirstNested og FirstNested.SecondNested) sammen med en ekstra, anonym indre klasse (inde FirstNested.SecondNested).

Hele den indlejrede brug starter med:

 offentlig StackTrace (boolsk na) {StackTrace.FirstNested nestet = ny StackTrace.FirstNested (); } 

Bemærk, at den boolske parameter (na) betyder intet. Jeg tilføjede det lige siden andre konstruktører skal skelnes.

Her er de indlejrede klasser:

 offentlig klasse FirstNested {public FirstNested () {StackTrace.displayStackTraceInformation (new Throwable ()); StackTrace.FirstNested.SecondNested yan = ny StackTrace.FirstNested.SecondNested (); System.out.println ("Dumping indefra hogwash ():"); yan.hogwash (); } offentlig klasse 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 ()); }}; // Slutningen af ​​den anonyme medlemsklasse. whacked.whack (); } // Slut på hogwash (). } // Afslutning af FirstNested.SecondNexted medlemsklasse. } // Afslutning af FirstNested-medlemsklassen. 

Det øverste stakelement til SecondNestedkonstruktør ser sådan ud:

Filnavn: StackTrace.java Linjenummer: 267 Pakkenavn: boo.hoo Fuldt klassenavn: boo.hoo.StackTrace $ FirstNested $ SecondNested Simple class name: StackTrace $ FirstNested $ SecondNested Unmunged class name: StackTrace.FirstNested.SecondNested Direct class name: SecondNested Method name: Native method ?: false toString (): boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) 

Du kan se, at det enkle klassenavn ikke er så simpelt i dette tilfælde. De indlejrede klasser skelnes fra de indlejrede klasser på højere niveau og fra den øverste klasse ved hjælp af dollartegnet ($). Så teknisk set er det "enkle" navn på den anden nestede klasse StackTrace $ FirstNested $ SecondNested.

Jeg har leveret unmungeSimpleClassName () metode til at erstatte dollartegn med perioder for fuldstændighed.

Da jeg er stædig, ville jeg stadig få det virkelig enkle klassenavn, så jeg oprettede extractDirectClassName ():