动态加载实现时如何使接口成为仅编译依赖项

How to make an interface a compile-only dependency when loading its implementation dynamically

考虑以下界面

// src/MyInterface.java

interface MyInterface {
    public void quack();
}

在下面的应用中动态使用;即它的实现是动态加载的——出于演示目的,我们将只使用实现 class' 名称来确定要加载哪个实现。

// src/Main.java

class Main {
    public static void main(String[] args) {
        try {
            MyInterface obj = (MyInterface) Class.forName("Implementation")
                                                 .getDeclaredConstructor()
                                                 .newInstance();
            obj.quack();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

以下接口实现可用:

// src/Implementation.java

class Implementation implements MyInterface {
    public void quack() {
        System.out.println("This is a sample implementation!");
    }
}

正如我的直觉所想,MyInterface 提供了仅在编译时相关的信息,例如可以在实现它的对象上调用哪些方法,但在 运行时间,因为它不提供任何 "executable code"。但事实并非如此:如果我尝试 运行 没有 MyInterface.class 的已编译 Main.class,它会抱怨:

$ javac -d bin/ src/*

$ rm bin/MyInterface.class

$ java -cp bin/ Main
Exception in thread "main" java.lang.NoClassDefFoundError: MyInterface
[...]

我想这是有道理的,因为它需要访问 MyInterfaceClass 对象来执行到 MyInterface 的转换,所以它需要加载 MyInterface .但我觉得应该有一种方法可以使它成为仅编译时依赖项。怎么样?


一些上下文

当我了解到可以存在仅编译时依赖项时,出现了这个问题,an example of which is the servlet api。我读到在编译 servlet 代码时,您需要有 servlet-api(在 Tomcat 的情况下)jar,但在 运行 时不需要它,因为服务器提供了一个实现。由于我不明白它究竟是如何工作的,所以我尝试进行上面的小实验。我是不是误解了什么意思?


编辑: this Gradle page 提到编译时唯一依赖项可能是

Dependencies whose API is required at compile time but whose implementation is to be provided by a consuming library, application or runtime environment.

有什么例子可以说明这一点?我觉得这句话有点令人困惑,因为它似乎暗示 API 在 运行 时 不需要 ,只有实现才是。从答案中,我认为这是不可能的,对吧? (除非以某种方式实现自定义 classloader?)

是的,看起来你误解了 servlet-api.jar 的例子。您在项目中需要它作为编译时依赖项,因为 Tomcat 本身带有该 jar,并且该 jar 将由 Tomcat 添加到 运行time 类路径。 如果您在代码中使用 classes/interfaces,它们应该以某种方式添加到类路径中,因为您的代码依赖于它们。

并且开始 Java 8 个接口可以有方法的默认实现("executable code")并且接口也可以有常量。

也许 运行 应用程序可以不声明接口,但在这种情况下,您需要开发自定义类加载器,它将检查接口实现并加载它而不是接口本身。

Did I misunderstand what that means?

是的。

您在谈论 "provided" 依赖项(至少 that's what Maven calls them)。这种依赖性仍然必须在编译时和运行时存在于 classpath/modulepath 上。但是,您不必在部署应用程序时将提供的依赖项包含在应用程序中,因为目标 container/framework 已经包含依赖项。