Java-Tipp 124: Verfolgen Sie Ihre Schritte in Java 1.4

Ich weiß nichts über dich, aber ich möchte wirklich gerne wissen, wo ich bin. Als Mann bin ich nie verloren, aber manchmal weiß ich einfach nicht, wo ich bin. Einige Orte, wie z. B. Einkaufszentren, haben Karten mit "You Are Here" -Anzeigen. In ähnlicher Weise können wir mit Java jetzt mithilfe der Systemhilfe unseren Standort ermitteln. In diesem Tipp zeige ich Ihnen, wie Sie diese Standortinformationen auf konsistente und zuverlässige Weise aus dem System extrahieren können.

Ich bin der festen Überzeugung, dass das Laufzeitsystem genügend Metadaten über das System selbst bereitstellen sollte, damit Programme bessere Entscheidungen treffen und Aufgaben erledigen können. Java ist seit einiger Zeit in der Lage, Klassen zu untersuchen und zu reflektieren, aber bisher fehlte es an der einfachen Möglichkeit, Laufzeitcode wieder seiner Position in der Quellcodedatei zuzuordnen. Die Lösung vor Java 1.4 bestand darin, den Stack-Trace einer Ausnahme manuell zu analysieren. Mit Java 1.4 haben wir jetzt eine bessere Lösung.

Stapelspur auseinander ziehen?

Die Problemumgehung vor Java 1.4 zum Sammeln von Standortinformationen bestand darin, die printStackTrace()Ausgabe einer Ausnahme manuell zu analysieren . Hier ist ein Beispiel für eine einfache Stapelverfolgung:

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

Das Auseinanderziehen des obigen Codes ist kein großes Analyseproblem. Aber wie wäre es damit?

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

Pfui. Was bedeutet all dieser seltsame Goobley-Guk wirklich und warum um alles in der Welt sollte ich ihn analysieren müssen? Offensichtlich verfolgt das System diese Standortinformationen bereits, da es diese Stapelspuren erstellen kann. Warum sind diese Standortinformationen nicht direkt verfügbar? Nun, mit Java 1.4 ist es endlich soweit.

Beachten Sie außerdem, dass angesichts von JIT-Compilern (Just-in-Time) und dynamischen, optimierenden Compilern wie dem HotSpot von Sun Microsystems möglicherweise keine Informationen zu Datei- und Zeilennummern vorhanden sind. Das Ziel "Leistung oder Pleite" kann sicherlich störend sein.

Java 1.4 Zur Rettung geworfen!

Nachdem Sun Microsystems jahrelange Beschwerden toleriert hatte, hat es die java.lang.ThrowableKlasse endlich um die getStackTrace()Methode erweitert. getStackTrace()Gibt ein Array von StackTraceElements zurück, wobei jedes StackTraceElementObjekt die Möglichkeit bietet, Standortinformationen mehr oder weniger direkt zu extrahieren.

Um diese Zuordnungsinformationen abzurufen, erstellen Sie weiterhin eine ThrowableInstanz an der Stelle, die für Ihren Code von Interesse ist:

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

Dieser Code positioniert diesen Punkt am Anfang von main().

Natürlich ist es sinnlos, nur diese Informationen zu sammeln, ohne etwas damit zu tun. In diesem Tipp verwenden wir jede zugrunde liegende Methode des StackTraceElements, um alle Informationen zu extrahieren und anzuzeigen, die wir können.

Das Beispielprogramm StackTrace.javazeigt anhand mehrerer Beispiele, wie die Standortinformationen extrahiert werden. Zum Kompilieren und Ausführen des Beispielprogramms benötigen Sie das J2SE (Java 2 Platform, Standard Edition) 1.4 SDK.

Zum Extrahieren und Anzeigen der Zuordnungsinformationen verwendet der Beispielcode eine Hilfsmethode displayStackTraceInformation()mit der folgenden grundlegenden Verwendungssprache:

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

Der displayStackTraceInformation()Code ist ziemlich einfach:

public static boolean displayStackTraceInformation (Throwable ex, boolean displayAll) {if (null == ex) {System.out.println ("Null-Stack-Trace-Referenz! Bailing ..."); falsch zurückgeben; } System.out.println ("Der Stapel gemäß printStackTrace (): \ n"); ex.printStackTrace (); System.out.println (""); StackTraceElement [] stackElements = ex.getStackTrace (); if (displayAll) {System.out.println ("Das Element" + stackElements.length + "+ ((stackElements.length == 1)?" ":" s ") +" des Stack-Trace: \ n " ); } else {System.out.println ("Das oberste Element eines" + stackElements.length + "Elementstapel-Trace: \ n"); } for (int lcv = 0; lcv <stackElements.length; lcv ++) {System.out.println ("Dateiname:" + stackElements [lcv] .getFileName ());System.out.println ("Zeilennummer:" + stackElements [lcv] .getLineNumber ()); String className = stackElements [lcv] .getClassName (); String packageName = extractPackageName (className); String simpleClassName = extractSimpleClassName (className); System.out.println ("Paketname:" + ("" .equals (Paketname)? "[Standardpaket]": Paketname)); System.out.println ("Vollständiger Klassenname:" + Klassenname); System.out.println ("Einfacher Klassenname:" + simpleClassName); System.out.println ("Name der nicht atmenden Klasse:" + unmungeSimpleClassName (simpleClassName)); System.out.println ("Direkter Klassenname:" + extractDirectClassName (simpleClassName)); System.out.println ("Methodenname:" + stackElements [lcv] .getMethodName ()); System.out.println ("Native Methode?:"+ stackElements [lcv] .isNativeMethod ()); System.out.println ("toString ():" + stackElements [lcv] .toString ()); System.out.println (""); if (! displayAll) gibt true zurück; } System.out.println (""); return true; } // Ende von displayStackTraceInformation ().

Grundsätzlich rufen wir getStackTrace()die übergebenen Daten auf Throwableund durchlaufen dann die einzelnen StackTraceElements, um so viele Zuordnungsinformationen wie möglich zu extrahieren.

Beachten Sie das Cruft-Bit, das der displayAllParameter einführt. displayAllLässt die aufrufende Site entscheiden, ob alle StackTraceElements oder nur das oberste Element des Stapels angezeigt werden sollen . Das Beispielprogramm verwendet den displayAllParameter, um die Ausgabe auf einen angemessenen Betrag zu beschränken.

Die meisten Stack-Trace-Informationen sind direkt nützlich. Beispielsweise gibt das StackTraceElement.getMethodName()eine Zeichenfolge zurück, die den Methodennamen enthält, während StackTraceElement.getFileName()eine Zeichenfolge mit dem ursprünglichen Quelldateinamen zurückgegeben wird. Lesen Sie das StackTraceElementJavadoc für die vollständige Liste der Methoden.

Klassennamen in Hülle und Fülle!

Wie Sie wahrscheinlich bemerkt haben, verwendet der displayStackTraceInformation()Code mehrere zusätzliche Hilfsmethoden, um den von zurückgegebenen Wert auseinander zu ziehen StackTraceElement.getClassName(). Diese Hilfsmethoden werden benötigt, da StackTraceElement.getClassName()der vollständig qualifizierte Klassenname zurückgegeben wird und StackTraceElementkeine anderen Methoden vorhanden sind, um die zugrunde liegenden Teile des vollständig qualifizierten Klassennamens bereitzustellen. Wir lernen jede zusätzliche Hilfsmethode kennen, indem wir die verschiedenen Verwendungsbeispiele von durcharbeiten displayStackTraceInformation().

Standard- oder benannte Pakete

Gibt unter Angabe des vollständig qualifizierten Klassennamens extractPackageName()den Namen des Pakets an, in dem sich die Klasse befindet:

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

Extrahiert im Grunde extractPackageNamealles vor dem letzten Punkt im vollständig qualifizierten Klassennamen. Diese vorhergehenden Informationen sind zufällig der Name des Pakets.

Hinweis: Sie können die Paketanweisung oben StackTrace.javain kommentieren / auskommentieren , um den Unterschied zwischen dem Ausführen des Beispielprogramms im unbenannten Standardpaket und dem Ausführen im boo.hooPaket zu untersuchen. Wenn dies nicht kommentiert ist, sollte die Anzeige des obersten Stapelelements für den Aufruf bar()von foo()von beispielsweise folgendermaßen main()aussehen:

Dateiname: StackTrace.java Zeilennummer: 227 Paketname: boo.hoo Vollständiger Klassenname: boo.hoo.StackTrace Einfacher Klassenname: StackTrace Name der nicht atmungsaktiven Klasse: StackTrace Direkter Klassenname: StackTrace Methodenname: bar Native Methode?: False toString ( ): boo.hoo.StackTrace.bar (StackTrace.java:227) 

Wenn Sie alternativ die Paketanweisung auskommentieren, sollte das obige Stapelelement folgendermaßen aussehen:

Dateiname: StackTrace.java Zeilennummer: 227 Paketname: [Standardpaket] Vollständiger Klassenname: StackTrace Einfacher Klassenname: StackTrace Name der nicht atmenden Klasse: StackTrace Direkter Klassenname: StackTrace Methodenname: bar Native Methode?: False toString (): StackTrace .bar (StackTrace.java:227) 

Können Klassennamen jemals einfach sein?

Die nächste Hilfsmethode, die wir verwenden, ist extractSimpleClassName(). Wie Sie sehen werden, sind die Ergebnisse dieser Methode nicht unbedingt einfach, aber ich möchte diesen vereinfachten Klassennamen klar vom vollständig qualifizierten Klassennamen unterscheiden.

Grundsätzlich extractSimpleClassName()ergänzt extractPackageName():

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

Mit anderen Worten, extractSimpleClassName()gibt alles nach dem letzten Punkt ( .) vom vollständig qualifizierten Klassennamen zurück. Zum Beispiel bar()sehen wir vom gleichen Aufruf bis oben, dass der einfache Klassenname genau ist StackTrace, unabhängig davon, ob der Code Teil des Standardpakets oder eines benannten Pakets ist oder nicht.

Wir erhalten interessantere Ergebnisse, wenn wir unsere Aufmerksamkeit auf verschachtelte Klassen richten. Im Beispielprogramm habe ich zwei Ebenen verschachtelter, benannter Klassen ( FirstNestedund FirstNested.SecondNested) zusammen mit einer zusätzlichen anonymen inneren Klasse (innen FirstNested.SecondNested) erstellt.

Die gesamte verschachtelte Verwendung beginnt mit:

public StackTrace (boolean na) {StackTrace.FirstNested nested = new StackTrace.FirstNested (); }}

Beachten Sie, dass der boolesche Parameter ( na) nichts bedeutet. Ich habe es gerade hinzugefügt, da andere Konstruktoren unterschieden werden müssen.

Hier sind die verschachtelten Klassen:

öffentliche Klasse FirstNested {public FirstNested () {StackTrace.displayStackTraceInformation (new Throwable ()); StackTrace.FirstNested.SecondNested yan = new StackTrace.FirstNested.SecondNested (); System.out.println ("Dumping from inside hogwash ():"); yan.hogwash (); } public class 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 ()); }}; // Ende der anonymen Mitgliedsklasse. whacked.whack (); } // Ende von hogwash (). } // Ende der FirstNested.SecondNexted-Memberklasse. } // Ende der FirstNested-Mitgliedsklasse.

Das oberste Stapelelement für SecondNestedden Konstruktor sieht folgendermaßen aus:

Dateiname: StackTrace.java Zeilennummer: 267 Paketname: boo.hoo Vollständiger Klassenname: boo.hoo.StackTrace $ FirstNested $ SecondNested Einfacher Klassenname: StackTrace $ FirstNested $ SecondNested Name der nicht überwachten Klasse: StackTrace.FirstNested.SecondNested Direkter Klassenname: Name der SecondNested-Methode: Native Methode?: False toString (): boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) 

Sie können sehen, dass der einfache Klassenname in diesem Fall nicht ganz so einfach ist. Die verschachtelten Klassen werden durch das Dollarzeichen ( $) von den übergeordneten verschachtelten Klassen und von der übergeordneten Klasse unterschieden . Technisch gesehen lautet der "einfache" Name der zweiten verschachtelten Klasse StackTrace$FirstNested$SecondNested.

Ich habe die unmungeSimpleClassName()Methode bereitgestellt , um die Dollarzeichen der Vollständigkeit halber durch Punkte zu ersetzen.

Da ich stur bin, wollte ich immer noch den wirklich einfachen Klassennamen haben, also habe ich Folgendes erstellt extractDirectClassName():