为什么编译 public 泄漏内部类型的 API 不会失败?

Why does compilation of public APIs leaking internal types not fail?

我有以下 Java 9 模块:

module com.example.a {
    exports com.example.a;
}

导出类型:

public class Api {

    public static void foo(ImplDetail args) {}
}

还有一个非导出类型:

package com.example.b.internal;

public class ImplDetail {}

导出类型使用非导出类型作为public方法中的方法参数类型。我假设编译器会拒绝这种不一致的 class 配置,因为其他模块中的客户端无法真正调用 foo() 方法,因为它们无法实例化参数类型。

令我惊讶的是,这个模块被javac编译成功了。我可以看到传递 null 的特殊情况,但我仍然认为这样的 API 定义格式错误,并且认为它不应该被支持,理想情况下由编译器强制执行。

不禁止这种情况的原因是什么?

当然,在 API 中使用非导出类型是不好的风格,很可能是设计错误,但我很清楚 javac 无法做到这一点编译时错误。

请注意,一直可以在 public API 中使用私有类型,一直追溯到 Java 1.0.

您已经注意到模块外的代码仍然可以调用 Api.foo(null)

在其他情况下,调用者仍然可以将此 API 与非空引用一起使用。考虑包 com.example.a 中的 class public class Sub extends ImplDetail。此 class Sub 是 public 并且已导出,因此可用于模块外的代码。因此,外部代码可以使用从某处获得的 Sub 实例调用 Api.foo(sub)

但是可以肯定的是,javac 可以判断任何导出的包中是否有任何 ImplDetail 的子类型,如果没有则发出编译时错误?不必要。由于单独编译的可能性,新的 classes 可能会在包含 Api 的编译步骤之后被引入到模块中。或者,就此而言,可以重新编译 module-info.class 文件以更改导出包的集合。

出于这些原因,我认为 javac 在编译 Api class 时引发错误是不合适的。然而,Javac 确实有一个选项 -Xlint:exports 可以将此类情况标记为警告。

在构建过程的后期,例如 jmod 工具,或一些事后模块审计工具,也可以标记在导出中使用非导出类型的使用 API .不过,我认为目前没有任何东西可以做到这一点。