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

我有以下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 {} 

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

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

不解除这种情况的原因是什么?

当然在API中使用非导出类型是错误的样式并且很可能是设计错误,但我很清楚javac无法使其成为编译时错误。

请注意,始终可以在公共API中使用私有类型,一直返回到Java 1.0。

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

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

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

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

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