编译时间与运行时间依赖关系 - Java

时间:2010-11-24 19:57:15

标签: java runtime classpath compile-time

Java中编译时和运行时依赖关系有什么区别? 它与类路径有关,但它们有何区别?

6 个答案:

答案 0 :(得分:67)

  • 编译时依赖性:您需要CLASSPATH中的依赖项来编译工件。生成它们是因为您对代码中的硬编码具有某种“引用”,例如为某些类调用new,扩展或实现某些内容(直接或间接),或者使用直接调用方法调用reference.method()符号。

  • 运行时依赖性:您需要CLASSPATH中的依赖项来运行工件。它们的生成是因为您执行访问依赖项的代码(以硬编码方式或通过反射或其他方式)。

尽管编译时依赖性通常意味着运行时依赖性,但您可以只具有编译时依赖性。这是基于Java仅在首次访问该类时链接类依赖性的事实,因此如果您从未在运行时访问特定类,因为从不遍历代码路径,Java将忽略该类及其依赖项。 / p>

此示例

在C.java中(生成C.class):

package dependencies;
public class C { }

在A.java中(生成A.class):

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());
        }
    }
}

在这种情况下,ACB具有编译时依赖性,但如果在执行时传递一些参数,它将只对C具有运行时依赖性java dependencies.A,因为当JVM执行B时,它只会尝试解决CB b = new B()的依赖性。此功能允许您在运行时仅提供您在代码路径中使用的类的依赖项,并忽略工件中其余类的依赖项。

答案 1 :(得分:30)

一个简单的例子就是看一下像servlet api这样的api。要使servlet编译,需要servlet-api.jar,但在运行时servlet容器提供了一个servlet api实现,因此您不需要将servlet-api.jar添加到运行时类路径中。

答案 2 :(得分:24)

编译器需要正确的类路径才能编译对库的调用(编译时依赖性)

JVM需要正确的类路径才能加载要调用的库中的类(运行时依赖项)。

它们可能有两种不同之处:

1)如果您的类C1调用库类L1,并且L1调用库类L2,那么C1对L1和L2具有运行时依赖性,但只对L1有编译时依赖性。

2)如果您的类C1使用Class.forName()或其他一些机制动态实例化接口I1,并且接口I1的实现类是类L1,那么C1对I1和L1具有运行时依赖性,但只有一个编译时间依赖于I1。

编译时和运行时相同的其他“间接”依赖项:

3)您的类C1扩展了库类L1,L1实现了接口I1并扩展了库类L2:C1对L1,L2和I1具有编译时依赖性。

4)您的类C1有一个方法foo(I1 i1)和一个方法bar(L1 l1),其中I1是一个接口,L1是一个接受I1参数的类:C1具有编译时依赖性在I1和L1上。

基本上,要做任何有趣的事情,您的类需要与类路径中的其他类和接口进行交互。由该组库接口形成的类/接口图产生编译时依赖关系链。库实现产生运行时依赖关系链。请注意,运行时依赖关系链是运行时依赖关系或失败缓慢的:如果L1的实现有时依赖于实例化类L2的对象,并且该类仅在一个特定场景中实例化,然后除了那个场景之外没有依赖。

答案 3 :(得分:11)

Java实际上并没有在编译时链接任何东西。它仅使用它在CLASSPATH中找到的匹配类来验证语法。直到运行时才将所有内容组合在一起并在当时基于CLASSPATH执行。

答案 4 :(得分:10)

Compiletime依赖项只是您正在编译的类中直接使用 的依赖项(其他类)。运行时依赖关系包括您正在运行的类的直接和间接依赖关系。因此,运行时依赖性包括依赖关系的依赖关系以及任何反射依赖关系,例如String中的类名,但在Class#forName()中使用。

答案 5 :(得分:1)

对于Java,编译时依赖性是源代码的依赖性。例如,如果类A从类B调用方法,则A在编译时依赖于B,因为A必须知道要编译的B(B的类型)。这里的诀窍应该是:编译代码还不是完整的可执行代码。它包括尚未编译或存在于外部jar中的源的可替换地址(符号,元数据)。在链接期间,这些地址必须由内存中的实际地址替换。要正确执行,应创建正确的符号/地址。这可以通过类(B)的类型来完成。我相信这是编译时的主要依赖。

运行时依赖性与实际控制流更相关。它会侵入实际的内存地址。这是程序运行时的依赖关系。您需要B类详细信息,例如实现,而不仅仅是类型信息。如果该类不存在,那么您将获得RuntimeException并且JVM将退出。

这两种依赖关系,一般而且不应该,流向同一个方向。这是OO设计的问题。

在C ++中,编译有点不同(不仅仅是及时),但它也有一个链接器。因此,我认为这个过程可能与Java类似。