请考虑下面的两个类: public class Strange1 { public static void main(String[] args) { try { Missing m = new Missing(). } catch (java.lang.NoClassDefFoundError ex) { System.out.println("Got it!"). } } }
public class Strange2 { public static void main(String[] args) { Missing m. try { m = new Missing(). } catch (java.lang.NoClassDefFoundError ex) { System.out.println("Got it!"). } } }
Strange1和Strange2都用到了下面这个类: class Missing { Missing() { } }
如果你编译所有这三个类,然后在运行Strange1和Strange2之前删除Missing.class文件,你就会发现这两个程序的行为有所不同。其中一个抛出了一个未被捕获的NoClassDefFoundError异常,而另一个却打印出了Got it! 到底哪一个程序具有哪一种行为,你又如何去解释这种行为上的差异呢? 程序Strange1只在其try语句块中提及Missing类型,因此你可能会认为它捕获NoClassDefFoundError异常,并打印Got it!另一方面,程序Strange2在try语句块之外声明了一个Missing类型的变量,因此你可能会认为所产生的NoClassDefFoundError异常不会被捕获。如果你试着运行这些程序,就会看到它们的行为正好相反:Strange1抛出了未被捕获的NoClassDefFoundError异常,而Strange2却打印出了Got it!怎样才能解释这些奇怪的行为呢? 如果你去查看Java规范以找出应该抛出NoClassDefFoundError异常的地方,那么你不会得到很多的指导信息。该规范描述道,这个错误可以“在(直接或间接)使用某个类的程序中的任何地方”抛出[JLS 12.2.1]。当VM调用Strange1和Strange2的main方法时,这些程序都间接使用了Missing类,因此,它们都在其权利范围内于这一点上抛出了该错误。 于是,本谜题的答案就是这两个程序可以依据其实现而展示出各自不同的行为。但是这并不能解释为什么这些程序在所有我们所知的Java实现上的实际行为,与你所认为的必然行为都正好相反。要查明为什么会是这样,我们需要研究一下由编译器生成的这些程序的字节码。 如果你去比较Strange1和Strange2的字节码,就会发现几乎是一样的。除了类名之外,唯一的差异就是catch语句块所捕获的参数ex与VM本地变量之间的映射关系不同。尽管哪一个程序变量被指派给了哪一个VM变量的具体细节会因编译器的不同而有所差异,但是对于和上述程序一样简单的程序来说,这些细节不太可能会差异很大。下面是通过执行javap -c Strange1命令而显示的Strange1.main的字节码: 0: new 3: dup 4: invokespecial #3. //Method Missing."":()V 7: astore_1 8: goto 20 11: astore_1 12: getstatic #5. // Field System.out:Ljava/io/PrintStream. 15: ldc #6. // String "Got it!" 17: invokevirtual #7.//Method PrintStream.println: (String). V 20: return Exception table: from to target type 0 8 11 Class java/lang/NoClassDefFoundError