有没有曾经迷路的经历?Java可以帮助你找到回去的路,至少是在编程上。以前,Java 缺乏一个简单的能力可以使运行中的代码返回它们在源代码中的位置。在JDK1.4以前的解决方法是人为去解析一个异常(exception)的stack trace,在该文章中会介绍在JDK1.4中使用getStackTrace()方法追踪运行代码的轨迹 人有时的确会迷路,比如在一些购物场内,经常有着“目前你的位置”类的指示牌,同样,Java现在也可以告诉你,你的代码现在运行到哪里了。这里,我将会详细为你讲解如何从系统中获得这些位置信息 在JDK1.4以前,我们在exception中手工添加printStackTrace()来获取此处的位置信息,这里给出一个由它产生的输出结果: java.lang.Throwable at boo.hoo.StackTrace.bar(StackTrace.java:223) at boo.hoo.StackTrace.foo(StackTrace.java:218) at boo.hoo.StackTrace.main(StackTrace.java:54) Pulling the above code apart is not a major parsing problem. But how about this? 上面的结果好像并不是问题的关键所在,那下面的一些结果怎么样呢? java.lang.Throwable at boo.hoo.StackTrace$FirstNested$SecondNested.<init>(StackTrace.java:267) at boo.hoo.StackTrace$FirstNested.<init>(StackTrace.java:256) at boo.hoo.StackTrace.<init>(StackTrace.java:246) at boo.hoo.StackTrace.main(StackTrace.java:70) 这些古怪的东西到底是什么,我该怎么使用它?显然系统已经得到了这些位置信息,系统已经可以构建整个的一个stack traces,但为什么这些信息不更直接的显示出来。现在在Java 1.4中,已经可以更直接的显示出来了。 还记得吗,在SUN的HotSpot这种JIT(即时just-in-time)编译器、动态加载、微调编译器中,文件和行号信息可能是不存在的,这令人有点烦恼 Java 1.4 的Throwable是它们的救星 在忍受多年之苦后,SUN终于决定扩展java.lang.Throwable的getStackTrace()方法,现在getStackTrace()会返回一组StackTraceElements,每个StackTraceElement对象提供或多或少的位置信息 要获得位置信息,你仍然需要在你需要知道位置信息的地方创建一个Throwable实例,如下 //... public static void main (String[] args) { Throwable ex = new Throwable(); //... This code positions that point at the start of main(). 这段代码位于main()方法的开头 当然,这样还什么都做不了,我们来试验StackTraceElement的所有主要方法,来显示出我们所有可以获得的信息 范例程序StackTrace.java 显示了获取位置信息的几个例子(你需要在JDK1.4环境编译它) 我们定义一个displayStackTraceInformation方法来解析显示位置信息,并按下面的形式使用它 //... public void crashAndBurnout() { //... displayStackTraceInformation (new Throwable()); //... } //... displayStackTraceInformation()方法很直接,如下 public static boolean displayStackTraceInformation (Throwable ex, boolean displayAll) { if (null == ex) { System.out.println ("Null stack trace reference! Bailing..."); return false; } System.out.println ("The stack according to printStackTrace(): "); ex.printStackTrace(); System.out.println (""); StackTraceElement[] stackElements = ex.getStackTrace(); if (displayAll) { System.out.println ("The " + stackElements.length + " element" + ((stackElements.length == 1) ? "": "s") + " of the stack trace: "); } else { System.out.println ("The top element of a " + stackElements.length + " element stack trace: "); } for (int lcv = 0; lcv < stackElements.length; lcv++) { System.out.println ("Filename: " + stackElements[lcv].getFileName()); System.out.println ("Line number: " + stackElements[lcv].getLineNumber()); String className = stackElements[lcv].getClassName(); String packageName = extractPackageName (className); String simpleClassName = extractSimpleClassName (className); System.out.println ("Package name: " + ("".equals (packageName)? "[default package]" : packageName)); System.out.println ("Full class name: " + className); System.out.println ("Simple class name: " + simpleClassName); System.out.println ("Unmunged class name: " + unmungeSimpleClassName (simpleClassName)); System.out.println ("Direct class name: " + extractDirectClassName (simpleClassName)); System.out.println ("Method name: " + stackElements[lcv].getMethodName()); System.out.println ("Native method?: " + stackElements[lcv].isNativeMethod()); System.out.println ("toString(): " + stackElements[lcv].toString()); System.out.println (""); if (!displayAll) return true; } System.out.println (""); return true; } // End of displayStackTraceInformation(). 我对传入的Throwable调用其getStackTrace()方法,得到StackTraceElements,并遍历出每个StackTraceElement,然后显示相应的信息 注意displayAll参数,displayAll决定是否显示所有的StackTraceElements,还是只显示最顶端的元素。 大多数的trace信息是有用的: StackTraceElement.getMethodName()返回所包含的方法名 StackTraceElement.getFileName() 返回所在文件名 阅读StackTraceElement的Javadoc 获取更多的方法介绍 你可能会注意到,displayStackTraceInformation()使用了几个方法才从StackTraceElement.getClassName()返回的值分离出类名,这是因为StackTraceElement.getClassName()方法返回的是类的全称,StackTraceElement没有提供直接返回单个类名的方法 是否声明package的比较 对给定的全称的名,extractPackageName()用来解析出其中的package名,如下 public static String extractPackageName (String fullClassName) { if ((null == fullClassName) || ("".equals (fullClassName))) return ""; int lastDot = fullClassName.lastIndexOf (′.′); if (0 >= lastDot) return ""; return fullClassName.substring (0, lastDot); } 可以看出上述的方法返回全称中的开头到最后一个点号的部分,也就是包名 注意:当你对StackTrace.java的package声明注释后,也就是没有声明package和声明package会有不同的结果出现,请加以比较,当声明package时,在调用bar()方法时stackelement的信息是 Filename: StackTrace.java Line number: 227 Package name: boo.hoo Full class name: boo.hoo.StackTrace Simple class name: StackTrace Unmunged class name: StackTrace Direct class name: StackTrace Method name: bar Native method?: false toString(): boo.hoo.StackTrace.bar(StackTrace.java:227) 如果没有声明package,那么结果是: Filename: StackTrace.java Line number: 227 Package name: [default package] Full class name: StackTrace Simple class name: StackTrace Unmunged class name: StackTrace Direct class name: StackTrace Method name: bar Native method?: false toString(): StackTrace.bar(StackTrace.java:227) extractSimpleClassName()方法是返回类名全称中的类名,也就是返回全称中最后一个点号后的部分,如下 public static String extractSimpleClassName (String fullClassName) { if ((null == fullClassName) || ("".equals (fullClassName))) return ""; int lastDot = fullClassName.lastIndexOf (′.′); if (0 > lastDot) return fullClassName; return fullClassName.substring (++lastDot); } 我们再来看看,如果是嵌套类,那情况会是怎样.在范例程序中,我创建了两个级别上的嵌套类,它们分别是FirstNested 和 FirstNested.SecondNested,在SecondNested中又定义了一个匿名类S 整个嵌套类的调用使用如下方法开始 The whole nested usage starts with: public StackTrace (boolean na) { StackTrace.FirstNested nested = new StackTrace.FirstNested(); } boolean参数na没有什么用,只是用来和其他的构建器区分,下面是该嵌套类的定义 public class 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()); } }; // End of anonymous member class. whacked.whack(); } // End of hogwash(). } // End of FirstNested.SecondNexted member class. } // End of FirstNested member class. 内部类SecondNested的构造器的 stackelement信息会显示如下: Filename: StackTrace.java Line number: 267 Package name: boo.hoo Full class name: boo.hoo.StackTrace$FirstNested$SecondNested Simple class name: StackTrace$FirstNested$SecondNested Unmunged class name: StackTrace.FirstNested.SecondNested Direct class name: SecondNested Method name: <init> Native method?: false toString(): boo.hoo.StackTrace$FirstNested$SecondNested.<init>(StackTrace.java:267) 你会发现“simple class name”这项不再是很简单了,内部类和自己的外部类以“$”符号相隔,这项的值就成了StackTrace$FirstNested$SecondNested 我们现在又提供了unmungeSimpleClassName()方法来把"$"用"."代替,如果还是想得到真正的类名怎么办?我又创建了一个方法extractDirectClassName(): public static String extractDirectClassName (String simpleClassName) { if ((null == simpleClassName) || ("".equals (simpleClassName))) return ""; int lastSign = simpleClassName.lastIndexOf (′$′); if (0 >s lastSign) return simpleClassName; return simpleClassName.substring (++lastSign); } 这个方法就是把"simple class name"这项的值中的从最后一个dollar符以后的部分分离出来,无论这个内部类上还有多少个嵌套类,这个方法都会显示出SecondNested 在Java中构建器都不特别命名,所以所有的构建器方法都会被命名为<init>,从上例中就可以看出 Anonymous inner classes(匿名的内部类)实际上是作为最顶层类的子类,对于上个例子,虽然在SecondNested有个匿名的内部类但从它的stackelement中可以看出它的层次 Filename: StackTrace.java Line number: 282 Package name: boo.hoo Full class name: boo.hoo.StackTrace$1 Simple class name: StackTrace$1 Unmunged class name: StackTrace.1 Direct class name: 1 Method name: whack Native method?: false toString(): boo.hoo.StackTrace$1.whack(StackTrace.java:282) 从结果可以看出该匿名类的上一级是StackTrace,而不是SecondNested(在StackTrace.java中 该匿名类是定义在SecondNested中的) 可以看到编译器对匿名类以数字编号作为类名,并把它放在对顶层类的下一级,如果要想知道它到底在哪个内部类的下级,就需要从这个stackelement数组的上一个元素找出信息,这个工作留给读者来完成 结论: 从这个范例你应该能基本了解如何显示出代码的运行位置,在实际使用中这只是对一些简单的日志和追踪有用,对更复杂的应用,我建议你使用Java新的Logging API或是Log4J,它们会提供更完善的信息,该文的更大意义在于使你更清楚一些基本概念 |
|