Почему компиляция открытых 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 искажено, и подумайте, что он не должен поддерживаться, в идеале компилятором.

В чем причина отказа от такого случая?

Ответ 1

Конечно, использование неэкспортированного типа в API - это плохой стиль и, скорее всего, будет ошибкой дизайна, но мне совершенно ясно, что javac не в состоянии сделать это ошибкой времени компиляции.

Обратите внимание, что всегда можно было использовать закрытый тип в общедоступном API, полностью возвращаясь к Java 1.0.

Вы уже отметили, что код вне модуля все равно может вызвать Api.foo(null).

Существуют и другие случаи, когда вызывающий может использовать этот API с ненулевой ссылкой. Рассмотрим класс public class Sub extends ImplDetail в пакете com.example.a. Этот класс Sub является общедоступным и экспортируется и поэтому доступен для кода вне модуля. Таким образом, внешний код может вызывать Api.foo(sub), используя экземпляры Sub, полученные откуда-то.

Но, конечно, javac может определить, есть ли какие-либо подтипы ImplDetail в любых экспортированных пакетах и ​​выдавать ошибку времени компиляции, если их нет? Не обязательно. Из-за возможности отдельной компиляции новые классы могут быть введены в модуль после этапа компиляции, который включает Api. Или, если на то пошло, файл module-info.class можно перекомпилировать для изменения набора экспортированных пакетов.

По этим причинам я считаю неприемлемым для javac для повышения ошибки в момент компиляции класса Api. Однако у Javac есть опция -Xlint:exports, которая будет отмечать такие случаи как предупреждение.

Что-то позже в процессе сборки, например, в инструменте jmod или в каком-то дополнительном модуле аудита модулей, можно также указать использование неэкспортируемого типа, используемого в экспортированном API. Я не думаю, что сейчас что-то делает.