An easy example is to look at an api like the servlet api. To make your servlets compile, you need the servlet-api.jar, but at runtime the servlet container provides a servlet api implementation so you do not need to add servlet-api.jar to your runtime class path.
package dependencies;
public class A {
public static class B {
public String toString() {
C c = new C();
return c.toString();
}
}
public static void main(String[] args) {
if (args.length > 0) {
B b = new B();
System.out.println(b.toString());
}
}
}
在这种情况下,A在编译时依赖于 C到 B,但是如果在执行 java dependencies.A时传递一些参数,它在运行时依赖于 C,因为 JVM 只会在执行 B b = new B()时尝试解决 B对 C的依赖。此特性允许您在运行时仅提供在代码路径中使用的类的依赖项,而忽略构件中其余类的依赖项。
对于 Java,编译时依赖性是源代码的依赖性。例如,如果 A 类从 B 类调用一个方法,那么 A 在编译时依赖于 B,因为 A 必须知道要编译的 B (B 类型)。这里的诀窍应该是: 编译的代码还不是一个完整的、可执行的代码。它包括尚未编译或存在于外部 jar 中的源的可替换地址(符号、元数据)。在链接期间,这些地址必须替换为内存中的实际地址。要做到这一点,正确的符号/地址应该创建。这可以通过类(B)的类型来实现。我相信这是编译时的主要依赖项。
运行时依赖性与实际的控制流更为相关。它涉及到实际的内存地址。这是程序运行时的一个依赖项。这里需要类 B 的详细信息,比如实现,而不仅仅是类型信息。如果该类不存在,那么您将得到 RuntimeException,并且 JVM 将退出。
这两种依赖关系通常和不应该流向同一个方向,但这是面向对象设计的问题。
在 C + + 中,编译稍有不同(不仅仅是及时编译) ,但是它也有一个链接器。所以我猜这个过程可能被认为类似于 Java。