我有两个例子:
示例1:
public class A {
}
public class B {
public void m(A a) {
}
}
public class C {
public static void main(String[] args) {
B b = new B();
System.out.println("hello!");
}
}
编译所有三个类。删除A.class。运行main。没有例外。
示例2:
public class D {
}
public class E {
public void omg(D d) {
}
public static void main(String[] args) {
E e = new E();
}
}
编译类。删除D.class。运行main方法。
Exception in thread "main" java.lang.NoClassDefFoundError: D
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.getMainMethod(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Caused by: java.lang.ClassNotFoundException: D
at java.net.URLClassLoader$1.run(Unknown Source)
为什么呢? D从未被引用过。
答案 0 :(得分:4)
JavaVM规范允许这两者。在Chapter 5. Loading, Linking, and Initializing我们有:
例如,Java虚拟机实现可以选择在使用它时分别解析类或接口中的每个符号引用(“延迟”或“延迟”解析),或者在类是被核实(“渴望”或“静态”解决)。
我的猜测是,Sun / Oracle选择对初始(“主”)类执行“静态”解析,因为很可能很快就会调用主类中的方法。
答案 1 :(得分:2)
您的班级引用方法D
中的班级public void omg(D d)
。
通常来自Sun / Oracle的JVM使用延迟解析,因此只要您不使用该方法就没关系,但是,您可以从堆栈跟踪中看到主要方法是通过反射操作搜索的Class.getMethod
会产生不同。
您可以使用以下代码进行验证:
public class B {
public static void main(String[] args) {
D.main(args);
}
}
class D {
public void foo(E e) {}
public static void main(String[] args) {
System.out.println("hello world");
}
}
class E { }
此处,在编译并运行E.class
后删除java B
不会产生错误。现在在代码中插入反射方法查找:
public class B {
public static void main(String[] args) {
try {
D.class.getMethod("main", String[].class);
} catch(NoSuchMethodException ex) {
ex.printStackTrace();
}
D.main(args);
}
}
class D {
public void foo(E e) {}
public static void main(String[] args) {
System.out.println("hello world");
}
}
class E { }
现在,在使用E
运行时,在编译后删除类java.lang.NoClassDefFoundError: E
会生成java B
。因此,手动触发的方法查找会重现原始代码示例中的行为,尽管类D
不是此处的main
类。
请注意,您可以通过从方法public
中删除foo
修饰符来解决问题。原因是Class.getMethod
仅考虑public
方法并跳过所有其他方法。
这也适用于原始代码示例:从方法public
中删除omg
会使NoClassDefFoundError
消失。