2016-12-16 15 views
12

У меня есть follwing Java 9 модуль:Почему компиляция открытых API-интерфейсов, протекающих во внутренних типах, не работает?

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

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

ответ

8

Конечно, использование неэкспортного типа в 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. Я не думаю, что в настоящее время все это делается.

+0

Спасибо за подробный ответ, Стюарт! Вывод экспортированного типа из неэкспортного типа кажется мне несовместимым, поэтому я бы сказал, что один должен гарантировать ошибку компиляции (для самого Sub). Но, вероятно, у вас будет такая же проблема, которую вы описываете несколькими этапами компиляции. – Gunnar

+0

@ Gunnar. Подумав об этом некоторое время, я понял, что разумно (если не редкость) иметь открытый класс, который является подклассом частного класса.В JDK, например, в java.lang, 'StringBuffer' и' StringBuilder' являются публичными подклассами 'AbstractStringBuilder', частного класса. Немного странно иметь иерархию SB <: ASB <: Объект с внутренним частным классом, но это сделано для совместного использования. Однако обратите внимание, что ASB не отображается как тип API. –

+0

Спасибо за последующее наблюдение. Лично я считаю такой дизайн неудобным. Пользователь типа должен иметь возможность видеть полную иерархию типа ИМО. Если речь идет о совместном использовании, это можно сделать путем делегирования в частный общий класс. – Gunnar