Глава 18. Вывод типов
Содержание
Разнообразные анализы на этапе компиляции требуют рассуждений о типах, которые еще неизвестны. Главными среди них являются проверка применимости методов с дженериками (§18.5.1) и вывод типа вызова методов с дженериками (§18.5.2). В общем случае, процесс рассуждений о неизвестных типах обозначается как вывод типов.
В общих чертах, вывод типов можно разделить на три процесса:
-
Сведение принимает утверждение о совместимости выражения или типа, называемое формулой ограничения, и сводит его к набору границ на переменных вывода. Часто, формула ограничения сводится к другим формулам ограничений, которые необходимо рекурсивно сводить. Следуется процедура, чтобы определить эти дополнительные формулы ограничений и, в конечном итоге, выразить с помощью набора границ условия, при которых выбранные типы, выведенные по умолчанию, будут делать каждую формулу ограничения истинной.
-
Включение поддерживает набор границ переменных вывода, гарантируя их согласованность по мере добавления новых границ. Поскольку границы одной переменной могут иногда влиять на возможные значения другой переменной, этот процесс распространяет границы между такими взаимозависимыми переменными.
-
Разрешение анализирует границы переменной вывода и определяет инстанциацию, которая совместима с этими границами. Также определяется порядок разрешения взаимозависимых переменных вывода.
Эти процессы тесно взаимодействуют: сведение может вызвать включение; включение может привести к дальнейшему сведению; и разрешение может вызвать дальнейшее включение.
-
§18.1 более точно определяет понятия, используемые как промежуточные результаты, и обозначения, используемые для их выражения.
-
§18.2 подробно описывает сведение.
-
§18.3 подробно описывает включение.
-
§18.4 подробно описывает разрешение.
-
§18.5 определяет, как эти инструменты вывода используются для решения определенных задач анализа на этапе компиляции.
По сравнению с изданием Java SE 7 Спецификации языка Java, важные изменения в выводе типов включают:
-
Добавление поддержки лямбда-выражений и ссылок на методы в качестве аргументов вызова методов.
-
Обобщение определения вывода в терминах поли-выражений, которые могут не иметь хорошо определенных типов до завершения вывода. Это оказывает заметное влияние на улучшение вывода для вложенных дженерик-методов и вызовов конструкторов с диамантами.
-
Описание того, как вывод используется для обработки целевых типов функциональных интерфейсов с подстановками и анализа наиболее специфичных методов.
-
Выяснение различия между проверкой применимости вызова (которая включает только аргументы вызова) и выводом типа вызова (который включает целевой тип).
-
Отсрочка разрешения всех переменных вывода, даже тех, у которых есть нижние границы, до вывода типа вызова, чтобы получить лучшие результаты.
-
Улучшение поведения вывода для взаимозависимых (или самозависимых) переменных.
-
Устранение ошибок и потенциальных источников путаницы. Эта редакция более тщательно и точно обрабатывает различие между конкретными контекстами преобразования и подтипированием и описывает сведение, параллельно с соответствующими не-выводными отношениями. Там, где существуют преднамеренные отклонения от не-выводных отношений, они явно идентифицируются как таковые.
-
Создание фундамента для будущего развития: усовершенствования или новые приложения вывода будет проще интегрировать в спецификацию.
В этом разделе определяются переменные вывода, формулы ограничений и границы, как эти термины будут использоваться в данной главе. Также здесь приводится обозначение.
Переменные вывода являются метапеременными для типов — то есть, это специальные имена, которые позволяют абстрактно рассуждать о типах. Для отличия от переменных типа, переменные вывода представлены греческими буквами, в основном α.
В данной главе термин «тип» используется свободно, включая подобную типам синтаксическую конструкцию, содержащую переменные вывода. Термин собственный тип исключает такие «типы», которые упоминают переменные вывода. Утверждения, включающие переменные вывода, являются утверждениями о каждом собственном типе, который может быть получен путем замены каждой переменной вывода собственным типом.
Формулы ограничений — это утверждения о совместимости или подтипах, которые могут включать переменные вывода. Формулы могут иметь один из следующих видов:
-
‹Выражение → T›: Выражение совместимо в контексте вызова с типом T (§5.3).
-
‹S → T›: Тип S совместим в контексте вызова с типом T (§5.3).
-
‹S ≤ T›: Ссылочный тип S является подтипом ссылочного типа T (§4.10).
-
‹S ⊂ T›: Аргумент типа S содержится в аргументе типа T (§4.5.1).
-
‹S = T›: Тип S такой же, как тип T (§4.3.4), или аргумент типа S такой же, как аргумент типа T.
-
‹LambdaExpression →throws T›: Проверяемые исключения, выбрасываемые телом LambdaExpression, объявляются в разделе
throwsтипа функции, полученного из T. -
‹MethodReference →throws T›: Проверяемые исключения, выбрасываемые ссылкой на метод, объявляются в разделе
throwsтипа функции, полученного из T.
Примеры формул ограничений:
-
Из
Collections.singleton("hi")у нас есть формула ограничения ‹"hi"→ α›. Через сокращение это станет формулой ограничения: ‹String<:α›. -
Из
Arrays.asList(1, 2.0)у нас есть формулы ограничений ‹1→ α› и ‹2.0→ α›. После сокращения эти станут формулами ограничений ‹int→ α› и ‹double→ α›, а затем ‹Integer<:α› и ‹Double<:α›. -
Из целевого типа вызова конструктора
List<Thread> lt = new ArrayList<>()у нас есть формула ограничения ‹ArrayList<α>→List<Thread>›. После сокращения это станет формулой ограничения ‹α<=Thread›, а затем ‹α =Thread›.
Во время процесса вывода сохраняется набор границ для переменных вывода. Граница имеет один из следующих форматов:
-
S = T, где хотя бы одна из S или T является переменной вывода: S равно T.
-
S
<:T, где хотя бы одна из S или T является переменной вывода: S является подтипом T. -
false: Нет допустимого выбора переменных вывода.
-
G
<α1, ..., αn>= capture(G<A1, ..., An>): Переменные α1, ..., αn представляют результат применения преобразования захвата (§5.1.10) к G<A1, ..., An>(где A1, ..., An могут быть типами или уайлдкардами и могут упоминать переменные вывода). -
throwsα: Переменная вывода α появляется вthrowsфрагменте.
Граница удовлетворяется заменой переменных вывода, если после применения замены утверждение становится истинным. Граница false никогда не может быть удовлетворена.
Некоторые границы связывают переменную вывода с корректным типом. Пусть T — корректный тип. При заданной границе вида α = T или T = α, мы говорим, что T является инстанциацией α. Аналогично, при заданной границе вида α <: T, мы говорим, что T является надлежащим верхним ограничением α, и при заданной границе вида T <: α, мы говорим, что T является надлежащим нижним ограничением α.
Другие границы связывают две переменные вывода или переменную вывода с типом, содержащим переменные вывода. Такие границы вида S = T или S <: T называются зависимостями.
Граница вида G<α1, ..., αn> = capture(G<A1, ..., An>) указывает, что α1, ..., αn являются плейсхолдерами для результатов преобразования захвата. Это необходимо, поскольку преобразование захвата может быть выполнено только над корректным типом, а переменные вывода в A1, ..., An могут еще не быть разрешены.
Граница вида throws α носит чисто информативный характер: она направляет решение на оптимизацию инстанциации α таким образом, чтобы, если это возможно, она не была типом проверяемого исключения.
Важным промежуточным результатом вывода является множество границ. Иногда удобно ссылаться на пустое множество границ с символом true; это просто для удобства, и эти два понятия взаимозаменяемы.
Примеры множеств границ:
-
{ α =
String} содержит единственную границу, инстанцирующую α какString. -
{
Integer<:α,Double<:α, α<:Object} описывает два надлежащих нижних ограничения и одно надлежащее верхнее ограничение для α. -
{ α
<:Iterable<?>, β<:Object, α<:List<β>} описывает надлежащее верхнее ограничение для каждого из α и β, а также зависимость между ними. -
{ } не содержит границ или зависимостей и может обозначаться как true.
-
{ false } выражает тот факт, что нет удовлетворительной инстанциации.
Когда вывод начинается, множество границ обычно генерируется из списка объявлений параметров типа P1, ..., Pp и соответствующих переменных вывода α1, ..., αp. Такое множество границ генерируется следующим образом. Для каждого l (1 ≤ l ≤ p):
-
Если Pl не имеет TypeBound, то граница αl
<:Objectпоявляется в множестве. -
В противном случае, для каждого типа T, ограниченного
&в TypeBound, граница αl<:T[P1:=α1, ..., Pp:=αp]появляется в множестве; если это приводит к отсутствию надлежащих верхних ограничений для αl (только зависимости), то граница αl<:Objectтакже появляется в множестве.
Редукция — это процесс упрощения набора формул ограничений (§18.1.2) для получения множества границ (§18.1.3).
Каждая формула ограничения рассматривается по очереди. Правила в этом разделе определяют, как формула сводится к одному или обоим из следующего:
-
Граница или множество границ, которые должны быть включены в "текущее" множество границ. Изначально текущее множество границ пусто.
-
Дополнительные формулы ограничений, которые должны быть редуцированы рекурсивно.
Редукция завершается, когда больше нет формул ограничений для редукции.
Результаты шага редукции всегда сохраняют корректность: если инстанциация переменной вывода удовлетворяет редуцированным ограничениям и границам, то она также удовлетворит исходному ограничению. С другой стороны, редукция не сохраняет полноту: могут существовать инстанциации переменных вывода, удовлетворяющие исходному ограничению, но не удовлетворяющие редуцированному ограничению или границе. Это обусловлено внутренними ограничениями алгоритма, а также желанием избежать чрезмерной сложности. Одним из последствий является то, что существуют выражения, для которых вывод аргументов типов не находит решения, но которые могут быть правильно типизированы, если программист явно вставляет соответствующие типы.
Формула ограничения вида ‹Выражение → T› сводится следующим образом:
-
Если T — правильный тип, то ограничение сводится к true, если выражение совместимо в контексте вызова с T (раздел §5.3), и к false в противном случае.
-
В противном случае, если выражение является самостоятельным выражением (раздел §15.2) типа S, ограничение сводится к ‹S → T›.
-
В противном случае, выражение является поливыражением (раздел §15.2). Результат зависит от формы выражения:
-
Если выражение является выражением в скобках вида
(Выражение'), ограничение сводится к ‹Выражение' → T›. -
Если выражение представляет собой выражение создания экземпляра класса или выражение вызова метода, ограничение сводится к набору связей B3, который используется для определения совместимости выражения с целевым типом T, как определено в §18.5.2.1. (Для выражения создания экземпляра класса соответствующий "метод" для вывода определён в §15.9.3.)
Этот набор связей может содержать новые переменные вывода, а также зависимости между этими новыми переменными и переменными вывода в T.
-
Если выражение представляет собой условное выражение вида
e1?e2:e3, ограничение сводится к двум формулам ограничений, ‹e2→ T› и ‹e3→ T›. -
Если выражение представляет собой лямбда-выражение или выражение ссылки на метод, результат указан ниже.
-
Если выражение представляет собой
switchвыражение с выражениями результатаe1, ...,en, ограничение сводится к n формулам ограничений, ‹e1→ T›, ..., ‹en→ T›.
-
Обработка вложенных вызовов обобщённых методов как поливыражений улучшает поведение вывода для вложенных вызовов. Например, следующее недопустимо в Java SE 7, но допустимо в Java SE 8:
ProcessBuilder b = new ProcessBuilder(Collections.emptyList()); // ProcessBuilder's constructor expects a List<String>
Когда оба внешний и вложенный вызовы требуют вывода, проблема становится сложнее. Например:
List<String> ls = new ArrayList<>(Collections.emptyList());
Наш подход заключается в "подъёме" выведенных ограничений для вложенного вызова (просто { α <: Object } в случае emptyList) в процесс внешнего вывода (в данном случае, попытка вывести β, где конструктор предназначен для типа ArrayList<β>). Мы также выводим зависимости между вложенными переменными вывода и внешними переменными вывода (ограничение ‹List<α> → Collection<β>› сводится к зависимости α = β). Таким образом, разрешение переменных вывода вложенного вызова может подождать, пока дополнительная информация не будет выведена из внешнего вызова (на основе целевого присваивания, β = String).
Формула ограничения вида ‹LambdaExpression → T›, где T упоминает по крайней мере одну переменную вывода, сводится следующим образом:
-
Если T не является типом функционального интерфейса (раздел §9.8), ограничение сводится к false.
-
В противном случае, пусть T' — целевой тип, полученный из T, как указано в §15.27.3. Если раздел §18.5.3 используется для вывода параметризованного типа функционального интерфейса, то проверка, что F
<A'1, ..., A'm>является подтипом F<A1, ..., Am>не выполняется (вместо этого она утверждается с помощью формулы ограничения ниже). Пусть целевой тип функции для лямбда-выражения — тип функции T'. Тогда:-
Если не найден допустимый тип функции, ограничение сводится к false.
-
В противном случае, конгруэнтность LambdaExpression с целевым типом функции утверждается следующим образом:
-
Если количество параметров лямбда-выражения отличается от количества типов параметров типа функции, ограничение сводится к false.
-
Если лямбда-выражение имеет неявный тип, и один или несколько типов параметров типа функции не являются правильными типами, ограничение сводится к false.
Это условие никогда не возникает на практике из-за обработки неявных типов лямбда-выражений в §18.5.1 и подстановки, применяемой к целевому типу в §18.5.2.2.
-
Если результатом типа функции является
void, а тело лямбда-выражения не является выражением-командой и не является блоком, совместимым с void, ограничение сводится к false. -
Если результатом типа функции не является
void, и тело лямбда-выражения является блоком, который не совместим по значению, ограничение сводится к false. -
В противном случае, ограничение сводится ко всем следующим формулам ограничений:
-
Если у лямбда-параметров есть явно объявленные типы F1, ..., Fn, а у типа функции — типы параметров G1, ..., Gn, то (i) для всех i (1 ≤ i ≤ n), ‹Fi = Gi›, и (ii) ‹T'
<:T›. -
Если типом результата типа функции является (не-
void) тип R, предположим, что типы параметров лямбда-выражения такие же, как типы параметров типа функции. Тогда:-
Если R — правильный тип, и если тело лямбда-выражения или какое-либо выражение результата в теле лямбда-выражения не совместимо в контексте присваивания с R, то false.
-
В противном случае, если R не является правильным типом, то где тело лямбда-выражения имеет вид Выражение, ограничение ‹Выражение → R›; или где тело лямбда-выражения — блок с выражениями результата
e1, ...,em, для всех i (1 ≤ i ≤ m), ‹ei→ R›.
-
-
-
Ключевая информация для получения из ограничения совместимости, включающего лямбда-выражение, — это набор ограничений на переменные вывода, присутствующие в типе результата целевого типа функции. Это имеет решающее значение, потому что функциональные интерфейсы часто являются обобщёнными, и многие методы, работающие с такими типами, также являются обобщёнными.
В простейшем случае лямбда-выражение может просто предоставить нижнюю границу для переменной вывода:
<T> List<T> makeThree(Factory<T> factory) { ... } String s = makeThree(()->"abc").get(2); -
В более сложных случаях выражение результата может быть поливыражением — возможно, даже другим лямбда-выражением — и поэтому переменная вывода может передаваться через несколько формул ограничений с различными целевыми типами перед тем, как будет произведено связывание.
Большая часть работы, описанной в этом разделе, предшествует утверждениям о выражениях результата; её цель — вывести тип функции лямбда-выражения и проверить выражения, явно несовместимые с ним.
Мы не пытаемся получить границы для переменных вывода, которые появляются в предложении throws целевого типа функции. Это связано с тем, что ограничение на наследование исключений не является частью совместимости (§15.27.3) — в частности, оно не должно влиять на применимость метода (§18.5.1). Однако мы получаем границы для этих переменных позже, потому что вывод типов вызова (§18.5.2.2) производит формулы ограничений на наследование исключений (§18.2.5).
Обратите внимание, что если целевой тип является переменной вывода или если типы параметров целевого типа содержат переменные вывода, мы получаем false. Во время вывода типов вызова (§18.5.2.2) выполняются дополнительные подстановки для инстанцирования этих переменных вывода, тем самым избегая этой ситуации. (Другими словами, редукция на практике никогда не будет «вызвана» с целевым типом одного из этих типов.)
Наконец, обратите внимание, что выражения результатов лямбда-выражения, согласно §15.27.3, должны быть совместимы в контексте присваивания с типом возврата целевого типа, R. Если R является собственным типом, например, Byte, полученным из Function, то проверка присваиваемости достаточно проста, и редукция выполняет её выше. Если R не является собственным типом, например, α полученный из <α,Byte>Function, то мы принимаем вышеприведённое упрощающее предположение, что слабая совместимость вызова будет достаточной. Разница между совместимостью присваивания и слабой совместимостью вызова заключается в том, что только присваивание допускает сужение константных выражений, таких как <String,α>Byte b = 100;. Следовательно, наше упрощающее предположение не сохраняет полноту: для целевого типа возврата α и целочисленного литерального выражения результата 100 можно предположить, что α может быть инстанцирован в Byte, но редукция фактически не будет генерировать такое ограничение.
Формула ограничения вида ‹MethodReference → T›, где T упоминает как минимум одну переменную вывода, сводится следующим образом:
-
Если T не является типом функционального интерфейса или если T является типом функционального интерфейса, не имеющим тип функции (§9.9), ограничение сводится к false.
-
В противном случае, если для ссылки на метод нет потенциально применимого метода при использовании T в качестве целевого типа, ограничение сводится к false.
-
В противном случае, если ссылка на метод точная (§15.13.1), то пусть P1, ..., Pn — типы параметров типа функции T, а F1, ..., Fk — типы параметров потенциально применимого метода. Ограничение сводится к новому набору ограничений следующим образом:
-
В специальном случае, когда n = k+1, параметр типа P1 должен действовать как целевая ссылка вызова. Выражение ссылки на метод обязательно имеет вид ReferenceType
::[TypeArguments] Identifier. Ограничение сводится к ‹P1<:ReferenceType› и, для всех i (2 ≤ i ≤ n), ‹Pi → Fi-1›.Во всех остальных случаях n = k, и ограничение сводится к следующему: для всех i (1 ≤ i ≤ n), ‹Pi → Fi›.
-
Если результат типа функции не является
void, пусть R — его тип возврата. Тогда, если результатом потенциально применимого объявления времени компиляции являетсяvoid, ограничение сводится к false. В противном случае ограничение сводится к ‹R' → R›, где R' — результат применения преобразования захвата (§5.1.10) к типу возврата потенциально применимого объявления времени компиляции.
-
-
В противном случае ссылка на метод неточная, и:
-
Если один или несколько типов параметров типа функции не являются собственными типами, ограничение сводится к false.
Это условие никогда не возникает на практике из-за обработки неточных ссылок на методы в §18.5.1 и подстановки, применённой к целевому типу в §18.5.2.2.
-
В противном случае выполняется поиск объявления времени компиляции, как указано в §15.13.1. Если для ссылки на метод нет объявления времени компиляции, ограничение сводится к false. В противном случае есть объявление времени компиляции, и: (пусть R — результат типа функции)
-
Если R является
void, ограничение сводится к true. -
В противном случае, если выражение ссылки на метод опускает TypeArguments, а объявление времени компиляции является обобщённым методом, и тип возврата объявления времени компиляции упоминает как минимум один из параметров типа метода, то:
-
Если R упоминает один из параметров типа метода, ограничение сводится к false.
В этом случае ограничение в терминах R может привести к связыванию переменной вывода типом переменной, находящейся вне области видимости. Поскольку инстанцирование переменной вывода с типом переменной, находящейся вне области видимости, бессмысленно, мы предпочитаем сразу отказаться от ситуации, когда такая возможность возникает. Это упрощение не сохраняет полноту.
-
Если R не упоминает один из параметров типа функции, тогда ограничение сводится к набору ограничений B3, который использовался бы для определения совместимости ссылки на метод при использовании типа возврата типа функции в качестве целевого типа, как определено в §18.5.2.1. B3 может содержать новые переменные вывода, а также зависимости между этими новыми переменными и переменными вывода в T.
Стратегия определения типа возврата обобщённого метода, на который ссылаются, следует шаблону, использованному ранее в этом разделе для вызовов обобщённых методов. Это может включать «поднятие» границ в внешний контекст и вывод зависимостей между двумя наборами переменных вывода.
-
-
В противном случае, пусть R' — результат применения преобразования захвата (§5.1.10) к типу возврата типа вызова (§15.12.2.6) объявления времени компиляции. Если R' является
void, ограничение сводится к false; в противном случае ограничение сводится к ‹R' → R›.
-
-
Формула ограничения вида ‹S → T› сводится следующим образом:
-
Если S и T — правильные типы, ограничение сводится к true, если S совместим в контексте вызова с T (раздел §5.3), и к false в противном случае.
-
В противном случае, если S — примитивный тип, пусть S' будет результатом применения преобразования автоупаковки (§5.1.7) к S. Тогда ограничение сводится к ‹S' → T›.
-
В противном случае, если T — примитивный тип, пусть T' будет результатом применения преобразования автоупаковки (§5.1.7) к T. Тогда ограничение сводится к ‹S = T'›.
-
В противном случае, если T — параметризованный тип вида G
<T1, ..., Tn>, и не существует типа вида G<...>, являющегося супертипом S, но при этом сам исходный тип G является супертипом S, то ограничение сводится к true. -
В противном случае, если T — тип массива вида G
<T1, ..., Tn>[]k, и не существует типа вида G<...>[]k, являющегося супертипом S, но при этом сам исходный тип G[]k является супертипом S, то ограничение сводится к true. (Обозначение[]k обозначает тип массива с k измерениями.) -
В противном случае, ограничение сводится к ‹S
<:T›.
Четвертый и пятый случаи — это неявное использование неявного преобразования (§5.1.9). Эти случаи, наряду с любым использованием неявного преобразования в первом случае, могут привести к предупреждениям о неявном преобразовании на этапе компиляции и могут повлиять на тип вызова метода (§15.12.2.6).
Автоупаковка T в T' не сохраняет полноту; например, если T было бы long, S могло бы быть инстанцировано как Integer, что не является подтипом Long, но может быть распаковано и затем расширено до long. Мы избегаем этой проблемы в большинстве случаев, предоставляя специальное лечение для типов возвращаемых значений переменных вывода, которые, как мы знаем, уже ограничены определёнными упакованными примитивными типами; см. §18.5.2.1.
Аналогично, обработка неявных преобразований жертвует полнотой в тех случаях, когда T не является параметризованным типом (например, если T — переменная вывода). В таких ситуациях обычно неясно, необходимо ли неявное преобразование или нет. Поскольку неявные преобразования вводят предупреждения о неявном преобразовании, вывод предпочтительнее избегать их, если это не явно необходимо.
Формула ограничения вида ‹S <: T› сводится следующим образом:
-
Если S и T — правильные типы, ограничение сводится к истинно, если S является подтипом T (§4.10), и к ложно в противном случае.
-
В противном случае, если S — тип null, ограничение сводится к истинно.
-
В противном случае, если T — тип null, ограничение сводится к ложно.
-
В противном случае, если S — переменная вывода, α, ограничение сводится к ограничению α
<:T. -
В противном случае, если T — переменная вывода, α, ограничение сводится к ограничению S
<:α. -
В противном случае, ограничение сводится в соответствии с формой T:
-
Если T — параметризованный класс или интерфейс, или тип внутреннего класса параметризованного класса или интерфейса (прямо или косвенно), пусть A1, ..., An — аргументы типа T. Среди надтипов S определяется соответствующий класс или интерфейс с аргументами типа B1, ..., Bn. Если такого типа не существует, ограничение сводится к ложно. В противном случае ограничение сводится к следующим новым ограничениям: для всех i (1 ≤ i ≤ n), ‹Bi
<=Ai›. -
Если T — любой другой класс или интерфейс, то ограничение сводится к истинно, если T входит среди надтипов S, и к ложно в противном случае.
-
Если T — тип массива, T'
[], то среди надтипов S, которые являются типами массивов, определяется наиболее специфичный тип S'[](это может быть сам S). Если такого типа массива не существует, ограничение сводится к ложно. В противном случае:-
Если ни S', ни T' не являются примитивным типом, ограничение сводится к ‹S'
<:T'›. -
В противном случае ограничение сводится к истинно, если S' и T' являются одним и тем же примитивным типом, и к ложно в противном случае.
-
-
Если T — переменная типа, есть три случая:
-
Если S — пересечение типов, элементом которого является T, ограничение сводится к истинно.
-
В противном случае, если у T есть нижняя граница, B, ограничение сводится к ‹S
<:B›. -
В противном случае ограничение сводится к ложно.
-
-
Если T — пересечение типов, I1
&...&In, ограничение сводится к следующим новым ограничениям: для всех i (1 ≤ i ≤ n), ‹S<:Ii›.
-
Формула ограничения вида ‹S <= T›, где S и T — аргументы типа (§4.5.1), сводится следующим образом:
-
Если T — тип:
-
Если S — тип, ограничение сводится к ‹S = T›.
-
Если S — подстановка, ограничение сводится к ложно.
-
-
Если T — подстановка вида
?, ограничение сводится к истинно. -
Если T — подстановка вида
?extendsT':-
Если S — тип, ограничение сводится к ‹S
<:T'›. -
Если S — подстановка вида
?, ограничение сводится к ‹Object<:T'›. -
Если S — подстановка вида
?extendsS', ограничение сводится к ‹S'<:T'›. -
Если S — подстановка вида
?superS', ограничение сводится к ‹Object= T'›.
-
-
Если T — подстановка вида
?superT':-
Если S — тип, ограничение сводится к ‹T'
<:S›. -
Если S — подстановка вида
?superS', ограничение сводится к ‹T'<:S'›. -
В противном случае ограничение сводится к ложно.
-
Формула ограничения вида ‹S = T›, где S и T — типы, сокращается следующим образом:
-
Если S и T являются собственными типами, ограничение сокращается до true, если S совпадает с T (§4.3.4), и false в противном случае.
-
В противном случае, если S или T — тип null, ограничение сокращается до false.
-
В противном случае, если S — переменная вывода α, а T — не примитивный тип, ограничение сокращается до связи α = T.
-
В противном случае, если T — переменная вывода α, а S — не примитивный тип, ограничение сокращается до связи S = α.
-
В противном случае, если S и T — классы или интерфейсы с одинаковой стираемой формой, где S имеет аргументы типа B1, ..., Bn, а T имеет аргументы типа A1, ..., An, ограничение сокращается до следующих новых ограничений: для всех i (1 ≤ i ≤ n), ‹Bi = Ai›.
-
В противном случае, если S и T — массивы типов, S'
[]и T'[], ограничение сокращается до ‹S' = T'›. -
В противном случае, если S и T — пересекающиеся типы, устанавливается соответствие между элементами S и элементами T. Элемент S, Si, соответствует элементу T, Tj, если Si и Tj являются либо одним и тем же типом, либо оба параметризациями одного и того же обобщенного класса или интерфейса, либо оба массивами типов.
Если каждому элементу S соответствует ровно один элемент T, и наоборот, то ограничение сокращается до следующих новых ограничений: для каждого элемента Si из S и соответствующего элемента Tj из T, ‹Si = Tj›. В противном случае ограничение сокращается до false.
Это правило не учитывает переменные вывода, появляющиеся непосредственно как элементы пересекающегося типа (а не вложенные в параметризованный тип). Из-за ограничений на объявления типов параметров (§4.4), такие пересекающиеся типы не возникают на практике.
-
В противном случае ограничение сокращается до false.
Формула ограничения вида ‹S = T›, где S и T — аргументы типа (§4.5.1), сокращается следующим образом:
-
Если S и T — типы, ограничение сокращается, как описано выше.
-
Если S имеет вид
?, а T имеет вид?, ограничение сокращается до true. -
Если S имеет вид
?, а T имеет вид?extendsT', ограничение сокращается до ‹Object= T'›. -
Если S имеет вид
?extendsS', а T имеет вид?, ограничение сокращается до ‹S' =Object›. -
Если S имеет вид
?extendsS', а T имеет вид?extendsT', ограничение сокращается до ‹S' = T'›. -
Если S имеет вид
?superS', а T имеет вид?superT', ограничение сокращается до ‹S' = T'›. -
В противном случае ограничение сокращается до false.
Формула ограничения вида ‹ВыражениеLambda →throws T› сводится следующим образом:
-
Если T не является типом функционального интерфейса (§9.8), ограничение сводится к false.
-
В противном случае, пусть целевой тип функции для лямбда-выражения определяется, как указано в §15.27.3. Если ни одного действительного типа функции найти не удаётся, ограничение сводится к false.
-
В противном случае, если лямбда-выражение имеет неявный тип, и один или несколько типов параметров типа функции не являются корректными типами, ограничение сводится к false.
Это условие никогда не возникает на практике из-за подстановки, применённой к целевому типу в §18.5.2.2.
-
В противном случае, если тип возвращаемого значения типа функции не является
voidи не является корректным типом, ограничение сводится к false.Это условие никогда не возникает на практике из-за подстановки, применённой к целевому типу в §18.5.2.2.
-
В противном случае, пусть E1, ..., En — типы в пункте
throwsтипа функции, которые не являются корректными типами. Если лямбда-выражение имеет неявный тип, пусть его типы параметров будут типами параметров функции. Если тело лямбда-выражения является поливыражением или блоком, содержащим полирезультатное выражение, пусть целевой тип возвращаемого значения будет типом возвращаемого значения функции. Пусть X1, ..., Xm — типы проверяемых исключений, которые может генерировать тело лямбда-выражения (§11.2). Тогда есть два случая:-
Если n =
0(пунктthrowsтипа функции состоит только из корректных типов), то если существует какое-то i (1 ≤ i ≤ m), такое что Xi не является подтипом ни одного корректного типа в пунктеthrows, ограничение сводится к false; в противном случае ограничение сводится к true. -
Если n >
0, ограничение сводится к набору ограничений подтипов: для всех i (1 ≤ i ≤ m), если Xi не является подтипом ни одного корректного типа в пунктеthrows, то ограничения включают, для всех j (1 ≤ j ≤ n), ‹Xi<:Ej›. Кроме того, для всех j (1 ≤ j ≤ n), ограничение сводится к границеthrowsEj.
-
Формула ограничения вида ‹СсылкаНаМетод →throws T› сводится следующим образом:
-
Если T не является типом функционального интерфейса или если T является типом функционального интерфейса, но не имеет типа функции (§9.9), ограничение сводится к false.
-
В противном случае, пусть целевой тип функции для выражения ссылки на метод будет типом функции T. Если ссылка на метод неточная (§15.13.1) и один или несколько типов параметров типа функции не являются корректными типами, ограничение сводится к false.
-
В противном случае, если ссылка на метод неточная и результат типа функции не является
voidи не является корректным типом, ограничение сводится к false. -
В противном случае, пусть E1, ..., En — типы в пункте
throwsтипа функции, которые не являются корректными типами. Пусть X1, ..., Xm — проверяемые исключения в пунктеthrowsтипа вызова компиляционного объявления ссылки на метод (§15.13.2) (как получено из типов параметров типа функции и типа возвращаемого значения). Тогда есть два случая:-
Если n =
0(пунктthrowsтипа функции состоит только из корректных типов), то если существует какое-то i (1 ≤ i ≤ m), такое что Xi не является подтипом ни одного корректного типа в пунктеthrows, ограничение сводится к false; в противном случае ограничение сводится к true. -
Если n >
0, ограничение сводится к набору ограничений подтипов: для всех i (1 ≤ i ≤ m), если Xi не является подтипом ни одного корректного типа в пунктеthrows, то ограничения включают, для всех j (1 ≤ j ≤ n), ‹Xi<:Ej›. Кроме того, для всех j (1 ≤ j ≤ n), ограничение сводится к границеthrowsEj.
-
Ограничения на проверяемые исключения обрабатываются отдельно от ограничений на типы возвращаемых значений, поскольку совместимость типов возвращаемых значений влияет на применимость методов (§18.5.1), в то время как исключения влияют только на тип вызова после завершения разрешения перегрузки (§18.5.2). Это можно упростить, включив совместимость исключений в определение совместимости лямбда-выражений (§15.27.3), но это приведет к возможно неожиданным случаям, в которых исключения, которые может сгенерировать тело явных лямбда-выражений, изменяют разрешение перегрузки.
Исключения, генерируемые телом лямбда-выражения, нельзя определить до тех пор, пока (i) не будут известны типы параметров лямбда-выражения и (ii) не будет известен целевой тип выражений результата в теле. (Второе требование относится к вызовам общих методов, в которых, например, один и тот же параметр типа появляется в типе возвращаемого значения и в пункте throws.) Следовательно, нам необходимо, чтобы оба эти, полученные из целевого типа T, были корректными типами.
Следствием является то, что лямбда-выражения, возвращаемые другими лямбда-выражениями, не могут генерировать ограничения из своих генерируемых исключений. Эти ограничения могут быть сгенерированы только из лямбда-выражений верхнего уровня.
Обратите внимание, что обработка случая, в котором в пункте throws типа функции появляется более одной переменной вывода, не сохраняет полноту. Каждая переменная по отдельности может удовлетворять ограничению, что каждое проверяемое исключение должно быть объявлено, но мы не можем быть уверены, какое из них предполагалось. Поэтому, для предсказуемости, мы ограничиваем их оба.
При генерации и росте связанных множеств во время вывода, возможно, что новые границы могут быть выведены на основе утверждений исходных границ. Процесс включения определяет эти новые границы и добавляет их в множество границ.
Включение может происходить в двух сценариях. Один сценарий — множество границ содержит пары дополнительных границ; это подразумевает новые формулы ограничений, как указано в §18.3.1. Другой сценарий — множество границ содержит границу, включающую преобразование захвата; это подразумевает новые границы и может подразумевать новые формулы ограничений, как указано в §18.3.2. В обоих сценариях любые новые формулы ограничений сводятся к минимуму, а любые новые границы добавляются в множество границ. Это может привести к дальнейшему включению; в конечном итоге множество достигнет фиксированной точки, и больше нельзя вывести новые границы.
Если включение множества границ достигло фиксированной точки, и множество не содержит границу false, тогда множество границ обладает следующими свойствами:
-
Для каждой комбинации собственной нижней границы
Lи собственной верхней границы U переменной вывода,L<:U. -
Если каждая переменная вывода, упомянутая в границе, имеет экземпляр, то граница удовлетворяется соответствующей заменой.
-
При заданной зависимости α = β, каждая граница α совпадает с границей β, и наоборот.
-
При заданной зависимости α
<:β, каждая нижняя граница α является нижней границей β, и каждая верхняя граница β является верхней границей α.
Утверждение о том, что включение достигает фиксированной точки, несколько упрощает ситуацию. Основываясь на работе Кеннеди и Пирса, О разрешимости номинального суботипирования с вариантами, это свойство можно доказать, сделав аргумент о том, что множество типов, которые могут появиться в множестве границ, конечно. Аргумент опирается на два предположения:
-
Новые переменные захвата не генерируются при сокращении ограничений суботипирования (§18.2.3).
-
Не рассматриваются расширяющиеся пути наследования.
Этот документ в настоящее время не гарантирует этих свойств (он неточен в отношении обработки подстановок при сокращении ограничений суботипирования и не обнаруживает расширяющиеся пути наследования), но может сделать это в будущей версии. (Это не новая проблема: алгоритм суботипирования Java также подвержен риску не завершения работы.)
(В этой части S и T — переменные вывода или типы, а U — правильный тип. Для краткости, граница вида α = T также может соответствовать границе вида T = α.)
Когда множество границ содержит пару границ, которые соответствуют одному из следующих правил, подразумевается новая формула ограничения:
-
α = S и α = T подразумевают ‹S = T›
-
α = S и α
<:T подразумевают ‹S<:T› -
α = S и T
<:α подразумевают ‹T<:S› -
S
<:α и α<:T подразумевают ‹S<:T› -
α = U и S = T подразумевают ‹S
[α:=U]= T[α:=U]› -
α = U и S
<:T подразумевают ‹S[α:=U]<:T[α:=U]›
Когда множество границ содержит пару границ α <: S и α <: T, и существует надтип S вида G<S1, ..., Sn> и надтип T вида G<T1, ..., Tn> (для некоторого обобщенного класса или интерфейса G), тогда для всех i (1 ≤ i ≤ n), если Si и Ti являются типами (а не подстановками), то формула ограничения ‹Si = Ti› подразумевается.
Когда множество ограничений содержит ограничение вида G<α1, ..., αn> = capture(G<A1, ..., An>), подразумеваются новые ограничения, и могут быть выведены новые формулы ограничений следующим образом.
Пусть P1, ..., Pn представляют параметр типа G, а B1, ..., Bn – ограничения этих параметров типа. Пусть θ представляет подстановку [P1:=α1, ..., Pn:=αn]. Пусть R – тип, который не является переменной вывода (но необязательно является корректным типом).
Множество ограничений на α1, ..., αn подразумевается, сгенерированное из объявленных ограничений P1, ..., Pn, как указано в §18.1.3.
Кроме того, для всех i (1 ≤ i ≤ n):
-
Если Ai не является подстановочным символом, то ограничение αi = Ai подразумевается.
-
Если Ai является подстановочным символом формы
?:-
αi = R подразумевает ограничение false
-
αi
<:R подразумевает формулу ограничения ‹Bi θ<:R› -
R
<:αi подразумевает ограничение false
-
-
Если Ai является подстановочным символом формы
?extendsT:-
αi = R подразумевает ограничение false
-
Если Bi является
Object, то αi<:R подразумевает формулу ограничения ‹T<:R› -
Если T является
Object, то αi<:R подразумевает формулу ограничения ‹Bi θ<:R› -
R
<:αi подразумевает ограничение false
-
-
Если Ai является подстановочным символом формы
?superT:-
αi = R подразумевает ограничение false
-
αi
<:R подразумевает формулу ограничения ‹Bi θ<:R› -
R
<:αi подразумевает формулу ограничения ‹R<:T›
-
Учитывая множество ограничений, не содержащее ограничение false, можно разрешить подмножество переменных вывода, упомянутых в множестве ограничений. Это означает, что для каждой переменной вывода может быть добавлена удовлетворительная подстановка до тех пор, пока у всех требуемых переменных не появятся подстановки.
Зависимости в множестве ограничений могут потребовать, чтобы переменные разрешались в определенном порядке или чтобы были разрешены дополнительные переменные. Зависимости указаны следующим образом:
-
Учитывая ограничение одного из следующих видов, где T – это либо переменная вывода β, либо тип, упоминающий β:
-
α = T
-
α
<:T -
T = α
-
T
<:α
Если α появляется слева от другого ограничения формы G
<..., α, ...>= capture(G<...>), то β зависит от разрешения α. В противном случае α зависит от разрешения β. -
-
Переменная вывода α, появляющаяся слева от ограничения вида G
<..., α, ...>= capture(G<...>) зависит от разрешения каждой другой переменной вывода, упомянутой в этом ограничении (с обеих сторон знака =). -
Переменная вывода α зависит от разрешения переменной вывода β, если существует переменная вывода γ такая, что α зависит от разрешения γ, а γ зависит от разрешения β.
-
Переменная вывода α зависит от разрешения самого себя.
Учитывая набор переменных вывода для разрешения, пусть V – объединение этого набора и всех переменных, от разрешения которых зависит разрешение по крайней мере одной переменной в этом наборе.
Если у каждой переменной в V есть подстановка, то разрешение успешно и эта процедура завершается.
В противном случае пусть { α1, ..., αn } – это непустое подмножество неинициализированных переменных в V такое, что (i) для всех i (1 ≤ i ≤ n), если αi зависит от разрешения переменной β, то либо β имеет подстановку, либо существует некоторое j такое, что β = αj; и (ii) нет непустого собственного подмножества { α1, ..., αn } с этим свойством. Разрешение происходит путем генерации подстановки для каждой из α1, ..., αn на основе ограничений в множестве ограничений:
-
Если связанное множество не содержит связи вида G
<..., αi, ...>= capture(G<...>) для всех i (1 ≤ i ≤ n), то для каждой αi определяется кандидатская инстанциация Ti:-
Если αi имеет одну или несколько строгих нижних границ,
L1, ...,Lk, то Ti = lub(L1, ...,Lk) (§4.10.4). -
В противном случае, если множество связей содержит
throwsαi, и каждая строгая верхняя граница αi является супертипомRuntimeException, то Ti =RuntimeException. -
В противном случае, где αi имеет строгие верхние границы U1, ..., Uk, Ti = glb(U1, ..., Uk) (§5.1.10).
Границы α1 = T1, ..., αn = Tn включаются в текущее множество связей.
Если результат не содержит связь false, то результат становится новым множеством связей, и разрешение продолжается путем выбора нового набора переменных для инстанциации (при необходимости), как описано выше.
В противном случае, результат содержит связь false, поэтому предпринимается вторая попытка инстанцировать { α1, ..., αn } путем выполнения шага ниже.
-
-
Если множество связей содержит связь вида G
<..., αi, ...>= capture(G<...>) для некоторого i (1 ≤ i ≤ n), или;Если множество связей, полученное на предыдущем шаге, содержит связь false;
то пусть Y1, ..., Yn — свежие переменные типов, границы которых определены следующим образом:
-
Для всех i (1 ≤ i ≤ n), если αi имеет одну или несколько строгих нижних границ
L1, ...,Lk, то пусть нижней границей Yi будет lub(L1, ...,Lk); в противном случае Yi не имеет нижней границы. -
Для всех i (1 ≤ i ≤ n), где αi имеет верхние границы U1, ..., Uk, пусть верхней границей Yi будет glb(U1 θ, ..., Uk θ), где θ — подстановка
[α1:=Y1, ..., αn:=Yn].
Если переменные типов Y1, ..., Yn не имеют хорошо сформированных границ (то есть нижняя граница не является подтипом верхней границы, или пересечение типов несовместимо), то разрешение терпит неудачу.
В противном случае, для всех i (1 ≤ i ≤ n), все связи вида G
<..., αi, ...>= capture(G<...>) удаляются из текущего множества связей, и связи α1 = Y1, ..., αn = Yn включаются.Если результат не содержит связь false, то результат становится новым множеством связей, и разрешение продолжается путем выбора нового набора переменных для инстанциации (при необходимости), как описано выше.
В противном случае, результат содержит связь false, и разрешение терпит неудачу.
-
Первый метод инстанцирования переменной вывода определяет инстанциацию из границ этой переменной. Однако иногда сложные зависимости означают, что результат не находится в границах переменной. В этом случае выполняется другой метод инстанциации, аналогичный преобразованию захвата (§5.1.10): вводятся свежие переменные типов с границами, выведенными из границ переменных вывода. Обратите внимание, что нижние границы этих переменных «захвата» вычисляются только с использованием строгих типов: это важно, чтобы избежать попыток выполнения вычислений типизации на неинициализированных переменных типа.
Используя описанные выше процессы вывода, следующие анализы выполняются во время компиляции.
Для вызова метода, не предоставляющего явных аргументов типа, процесс определения применимости потенциально применимого обобщенного метода m выполняется следующим образом:
-
Пусть P1, ..., Pp (p ≥ 1) являются параметрами типа
m, пусть α1, ..., αp — переменные вывода, и пусть θ — подстановка[P1:=α1, ..., Pp:=αp]. -
Набор начальных ограничений B0 генерируется из объявленных ограничений P1, ..., Pp, как описано в §18.1.3.
-
Для всех i (1 ≤ i ≤ p), если Pi появляется в
throws-записиm, то подразумевается ограничениеthrowsαi. Эти ограничения, если они есть, включаются в B0, образуя новый набор ограничений B1. -
Множество формул ограничений C генерируется следующим образом.
Пусть F1, ..., Fn — типы формальных параметров
m, и пустьe1, ...,ek— выражения фактических аргументов вызова. Тогда:-
Для проверки применимости по строгому вызову:
Если k ≠ n, или если существует i (1 ≤ i ≤ n) такое, что
eiимеет отношение к применимости (§15.12.2.2) и либо (i)eiявляется самостоятельным выражением примитивного типа, но Fi — ссылочный тип, или (ii) Fi — примитивный тип, ноeiне является самостоятельным выражением примитивного типа; метод не применим, и нет необходимости продолжать вывод.В противном случае, C включает ‹
ei→ Fi θ› для всех i (1 ≤ i ≤ k), гдеeiимеет отношение к применимости. -
Для проверки применимости по свободному вызову:
Если k ≠ n, метод не применим, и нет необходимости продолжать вывод.
В противном случае, C включает ‹
ei→ Fi θ› для всех i (1 ≤ i ≤ k), гдеeiимеет отношение к применимости. -
Для проверки применимости по вызову с переменной арностью:
Пусть F'1, ..., F'k — первые k типы параметров с переменной арностью
m(§15.12.2.4). C включает ‹ei→ F'i θ› для всех i (1 ≤ i ≤ k), гдеeiимеет отношение к применимости.
-
-
C упрощается (§18.2) и полученные ограничения включаются в B1, образуя новый набор ограничений B2.
-
Наконец, метод
mприменим, если B2 не содержит ограничение false и разрешение всех переменных вывода в B2 успешно (§18.4).
Рассмотрим следующий вызов метода и присваивание:
List<Number> ln = Arrays.asList(1, 2.0);
Необходимо определить наиболее специфичный применимый метод для вызова, как описано в §15.12. Единственный потенциально применимый метод (§15.12.2.1) объявлен следующим образом:
public static <T> List<T> asList(T... a)
Тривиально (из-за своей арности), этот метод не применим ни по строгому вызову (§15.12.2.2), ни по свободному вызову (§15.12.2.3). Но поскольку других кандидатов нет, на третьем этапе проверяется применимость метода по вызову с переменной арностью.
Начальный набор ограничений B — тривиальное верхнее ограничение для одной переменной вывода α:
{ α <: Object }
Начальное множество формул ограничений следующее:
{ ‹1 → α›, ‹2.0 → α› }
Они упрощаются до нового набора ограничений B1:
{ α <: Object, Integer <: α, Double <: α }
Затем, для проверки применимости метода, мы пытаемся разрешить эти ограничения. Мы успешно получаем довольно сложное подстановку
α = Number & Comparable<?
extends Number & Comparable<?>>
Таким образом, мы продемонстрировали, что метод применим; поскольку других кандидатов нет, он является наиболее специфичным применимым методом. Тем не менее, тип вызова метода и его совместимость с целевым типом в присваивании не определяются до тех пор, пока не произойдет дальнейший вывод, как описано в следующем разделе.
Для выражения вызова метода, не предоставляющего явных аргументов типа, и соответствующего наиболее специфичному применимому обобщенному методу m, процесс вывода типа вызова (§15.12.2.6) выбранного метода может потребовать разрешения дополнительных ограничений, как для утверждения совместимости с целевым типом, так и для утверждения правильности выражений аргументов вызова метода.
Важно отметить, что для нахождения типа вызова метода используются несколько "раундов" вывода. Это необходимо, например, для того, чтобы позволить целевому типу влиять на тип вызова, не позволяя ему влиять на выбор применимого метода. Первый раунд (§18.5.1) создаёт набор ограничений и проверяет, существует ли разрешение, но не принимает это разрешение. Последующие раунды уменьшают дополнительные ограничения, пока окончательный шаг разрешения не определит "настоящий" тип выражения.
Если выражение вызова метода является полиморфным выражением (§15.12), его совместимость с целевым типом T определяется следующим образом.
Если выражение вызова метода появляется в строгом контексте вызова, и T является примитивным типом, то выражение не совместимо с T.
В противном случае:
-
Пусть B2 — множество ограничений, полученное путём редукции, чтобы продемонстрировать, что
mприменимо в §18.5.1.(Хотя в §18.5.1 необходимо было продемонстрировать, что переменные вывода в B2 могут быть разрешены, чтобы установить применимость, подстановки, полученные на этом шаге разрешения, не считаются частью B2.)
-
Пусть B3 — множество ограничений, полученное из B2 следующим образом.
Пусть R — возвращаемый тип
m, и пусть θ — подстановка[P1:=α1, ..., Pp:=αp], определённая в §18.5.1 для замены параметров типаmна переменные вывода, и пусть T — целевой тип вызова. Тогда:-
Если для применимости метода во время редукции множества ограничений в §18.5.1 была необходима неявная конвертация, то формула ограничения ‹|R| → T› редуцируется и включается в B2.
-
В противном случае, если R θ является параметризованным типом, G
<A1, ..., An>, и один из A1, ..., An является джокером, то для свежих переменных вывода β1, ..., βn формула ограничения ‹G<β1, ..., βn>→ T› редуцируется и включается, вместе с ограничением G<β1, ..., βn>= capture(G<A1, ..., An>), в B2. -
В противном случае, если R θ является переменной вывода α, и выполняется одно из следующих условий:
-
T — ссылка на тип, но не является типом с параметром-джокером, и либо (i) B2 содержит ограничение одного из видов α = S или S
<:α, где S — тип с параметром-джокером, или (ii) B2 содержит два ограничения вида S1<:α и S2<:α, где S1 и S2 имеют супертипы, которые являются двумя различными параметризациями одного и того же генерического класса или интерфейса. -
T — параметризация генерического класса или интерфейса G, и B2 содержит ограничение одного из видов α = S или S
<:α, где не существует типа вида G<...>, который является супертипом S, но необработанный тип |G<...>| является супертипом S. -
T — примитивный тип, и один из примитивных оберток, упомянутых в §5.1.7, является инстанциацией, верхней или нижней границей для α в B2.
тогда α разрешается в B2, и где захват полученной инстанциации α равен U, формула ограничения ‹U → T› редуцируется и включается в B2.
-
-
В противном случае, формула ограничения ‹R θ → T› редуцируется и включается в B2.
-
-
Выражение вызова метода совместимо с T, если B3 не содержит ограничение false и разрешение всех переменных вывода в B3 успешно (§18.4).
Рассмотрим пример из предыдущего раздела:
List<Number>ln = Arrays.asList(1, 2.0);
Самый подходящий метод был определён как:
public static<T>List<T>asList(T... a)
Для завершения проверки типа вызова метода необходимо определить, совместим ли он со своим целевым типом, List. <Number>
Множество ограничений, использованное для демонстрации применимости в предыдущем разделе, B2, было:
{ α <: Object, Integer <: α, Double <: α }
Новое множество формул ограничений таково:
{ ‹List → <α>List› } <Number>
Это ограничение совместимости порождает равенство для α, которое включается в новое множество ограничений B3:
{ α <: Object, Integer <: α, Double <: α, α = Number }
Эти ограничения тривиально разрешаются:
α = Number
Наконец, мы выполняем подстановку в объявленном возвращаемом типе asList, чтобы определить, что вызов метода имеет тип List; очевидно, это совместимо с целевым типом. <Number>
Эта стратегия вывода отличается от Java SE 7 Edition Спецификации языка Java®, которая бы инстанцировала α на основе его нижних границ (даже не рассматривая целевой тип вызова), как мы делали в предыдущем разделе. Это привело бы к ошибке типа, так как полученный тип не является подтипом List. <Number>
В различных особых случаях, исходя из ограничений, присутствующих в B2, мы немедленно разрешаем переменную вывода, которая появляется в качестве возвращаемого типа вызова. Это для того, чтобы избежать нежелательных ситуаций, в которых обычное ограничение ‹R θ → T› не сохраняет полноту. К сожалению, возможно, что немедленным разрешением переменной мы не можем воспользоваться ограничениями, которые были бы выведены позже. Также возможно, что в некоторых случаях ограничения, которые позже будут выведены из аргументов вызова (например, неявно типизированные лямбда-выражения), привели бы к другому результату, если бы они были присутствовали в B2. Несмотря на эти ограничения, стратегия позволяет получить разумные результаты в типичных случаях использования и совместима со спецификацией языка Java® Java SE 7 Edition.
Тип вызова выбранного метода определяется после рассмотрения дополнительных ограничений, которые могут быть подразумеваемы выражениями аргументов в выражении вызова метода, следующим образом:
-
Если выражение вызова метода является поли-выражением, пусть B3 — множество ограничений, сгенерированное в §18.5.2.1 для демонстрации совместимости с фактическим целевым типом вызова метода.
Если выражение вызова метода не является поли-выражением, пусть B3 — то же самое множество ограничений, полученное путём редукции, чтобы продемонстрировать, что
mприменимо в §18.5.1.(Хотя в §18.5.1 и §18.5.2.1 было необходимо продемонстрировать, что переменные вывода в множестве ограничений могут быть разрешены, экземпляры, полученные в результате этих шагов разрешения, не считаются частью B3.)
-
Множество формул ограничений, C, генерируется следующим образом.
Пусть
e1, ...,ek— фактические выражения аргументов выражения вызова метода.Если
mприменимо по строгому или свободному вызову, пусть F1, ..., Fk — типы формальных параметровm; еслиmприменимо по вызову с переменным числом аргументов, пусть F1, ..., Fk — первые k типов параметров с переменным числом аргументовm(§15.12.2.4).Пусть θ — подстановка
[P1:=α1, ..., Pp:=αp], определённая в §18.5.1 для замены параметров типаmна переменные вывода.Тогда для всех i (1 ≤ i ≤ k):
-
Если
eiне относится к применимости, C содержит ‹ei→ Fi θ›. -
Дополнительные ограничения могут быть включены в зависимости от формы
ei:-
Если
ei— LambdaExpression, C содержит ‹LambdaExpression →throws Fi θ›, и в теле лямбда-выражения ищут дополнительные ограничения:-
Для тела лямбды-блока поиск применяется рекурсивно к каждому его выражению результата.
-
Для выражения создания экземпляра поли-класса или поли-вызова метода C содержит все формулы ограничений, которые появились бы в множестве C, сгенерированном §18.5.2 при выводе типа вызова поли-выражения.
-
Для выражения в скобках поиск применяется рекурсивно к содержащемуся выражению.
-
Для условного выражения поиск применяется рекурсивно ко второму и третьему операндам.
-
Для лямбда-выражения поиск применяется рекурсивно к телу лямбды.
-
Для выражения
switch, поиск применяется рекурсивно к каждому его выражению результата.
-
-
Если
ei— MethodReference, C содержит ‹MethodReference →throws Fi θ›. -
Если
ei— выражение создания экземпляра поли-класса или поли-вызова метода, C содержит все формулы ограничений, которые появились бы в множестве C, сгенерированном §18.5.2 при выводе типа вызова поли-выражения. -
Если
ei— выражение в скобках, эти правила применяются рекурсивно к содержащемуся выражению. -
Если
ei— условное выражение, эти правила применяются рекурсивно ко второму и третьему операндам. -
Если
ei— выражениеswitch, эти правила применяются рекурсивно к каждому из его выражений результата.
-
-
-
Пока C не пусто, повторяется следующий процесс, начиная с множества ограничений B3 и накапливая новые ограничения в текущее множество ограничений, в конечном итоге создавая новое множество ограничений B4:
-
Выбирается подмножество ограничений в C, удовлетворяющее свойству, что для каждого ограничения ни одна входная переменная не может влиять на выходную переменную другого ограничения в C. Термины входная переменная и выходная переменная определены ниже. Переменная вывода α может влиять на переменную вывода β, если α зависит от разрешения β (§18.4), или наоборот; или если существует третья переменная вывода γ такая, что α может влиять на γ, а γ может влиять на β.
Если это подмножество пусто, то в графе зависимостей между ограничениями есть цикл (или циклы). В этом случае ограничения в C, участвующие в цикле зависимости (или циклах) и не зависящие от каких-либо ограничений за пределами цикла (или циклов), рассматриваются. Одно ограничение выбирается из этих рассмотренных ограничений следующим образом:
-
Если какие-либо из рассмотренных ограничений имеют вид ‹Выражение → T›, то выбранным ограничением является рассмотренное ограничение этого вида, содержащее выражение слева (§3.5) выражения каждого другого рассмотренного ограничения такого вида.
-
Если ни одно из рассмотренных ограничений не имеет вид ‹Выражение → T›, то выбранным ограничением является рассмотренное ограничение, содержащее выражение слева от выражения каждого другого рассмотренного ограничения.
-
-
Выбранное ограничение (или ограничения) удаляется из C.
-
Входные переменные α1, ..., αm всех выбранных ограничений разрешаются.
-
Где T1, ..., Tm являются экземплярами α1, ..., αm, подстановка
[α1:=T1, ..., αm:=Tm]применяется ко всем ограничениям. -
Ограничение (или ограничения), полученное в результате подстановки, редуцируется и включается в текущее множество ограничений.
-
-
Наконец, если B4 не содержит связь false, переменные вывода в B4 разрешаются.
Если разрешение успешно с инстанцированиями T1, ..., Tp для переменных вывода α1, ..., αp, пусть θ' будет подстановкой
[P1:=T1, ..., Pp:=Tp]. Тогда:-
Если неконтролируемое преобразование было необходимо для того, чтобы метод был применим во время сокращения набора ограничений в §18.5.1, то типы параметров типа вызова
mполучаются путем применения θ' к типам параметров типаm, а тип возвращаемого значения и типы исключений типа вызоваmдаются путем стирания типа возвращаемого значения и типов исключений типаm. -
Если неконтролируемое преобразование не было необходимо для того, чтобы метод был применим, то тип вызова
mполучается путем применения θ' к типуm.
Если B4 содержит связь false, или если разрешение не удается, то возникает ошибка времени компиляции.
-
Процесс уменьшения дополнительных ограничений аргументов может потребовать тщательного упорядочения формул ограничений видов ‹Expression → T›, ‹LambdaExpression →throws T› и ‹MethodReference →throws T›. Чтобы облегчить этот порядок, входные переменные этих ограничений определяются следующим образом:
-
Для ‹LambdaExpression → T›:
-
Если T является переменной вывода, она является (единственной) входной переменной.
-
Если T является типом функционального интерфейса и тип функции может быть получен из T (§15.27.3), тогда входные переменные включают (i) если лямбда-выражение неявно типизировано, переменные вывода, упомянутые типами параметров функции; и (ii) если тип возвращаемого значения функции, R, не равен
void, то для каждого результирующего выраженияeв теле лямбды (или для самого тела, если оно является выражением), входные переменные ‹e→ R›. -
В противном случае входные переменные отсутствуют.
-
-
Для ‹LambdaExpression →throws T›:
-
Если T является переменной вывода, она является (единственной) входной переменной.
-
Если T является типом функционального интерфейса и может быть получен тип функции, как описано в §15.27.3, входные переменные включают (i) если лямбда-выражение неявно типизировано, переменные вывода, упомянутые типами параметров функции; и (ii) переменные вывода, упомянутые типом возвращаемого значения функции.
-
В противном случае входные переменные отсутствуют.
-
-
Для ‹MethodReference → T›:
-
Если T является переменной вывода, она является (единственной) входной переменной.
-
Если T является типом функционального интерфейса с типом функции, и если ссылка на метод неточна (§15.13.1), входные переменные являются переменными вывода, упомянутыми типами параметров функции.
-
В противном случае входные переменные отсутствуют.
-
-
Для ‹MethodReference →throws T›:
-
Если T является переменной вывода, она является (единственной) входной переменной.
-
Если T является типом функционального интерфейса с типом функции, и если ссылка на метод неточна (§15.13.1), входные переменные являются переменными вывода, упомянутыми типами параметров функции и типом возвращаемого значения функции.
-
В противном случае входные переменные отсутствуют.
-
-
Для ‹Expression → T›, если Expression является выражением в скобках:
Если содержащееся выражение Expression является Expression', входные переменные являются входными переменными ‹Expression' → T›.
-
Для ‹ConditionalExpression → T›:
Если условное выражение имеет форму
e1?e2:e3, входными переменными являются входные переменные ‹e2→ T› и ‹e3→ T›. -
Для ‹SwitchExpression → T›:
Если у выражения
switchесть результирующие выраженияe1, ...,en, входные переменные, для всех i (1 ≤ i ≤ n), входные переменные ‹ei→ T›. -
Для всех других формул ограничений входные переменные отсутствуют.
Выходные переменные этих ограничений - все переменные вывода, упомянутые типом в правой части ограничения, T, которые не являются входными переменными.
В случае лямбда-выражения с явными типами параметров P1, ..., Pn, которое направлено на тип функционального интерфейса F<A1, ..., Am> с как минимум одним аргументом типа дикого символа, то параметризация F может быть выведена как целевой тип лямбда-выражения следующим образом.
Пусть Q1, ..., Qk — типы параметров типа функции типа F<α1, ..., αm>, где α1, ..., αm — свежие переменные вывода.
Если n ≠ k, то допустимая параметризация отсутствует. В противном случае формируется набор формул ограничений, состоящий из ‹Pi = Qi› для всех i (1 ≤ i ≤ n). Этот набор формул ограничений сводится к набору ограничений B.
Если B содержит ограничение false, то допустимая параметризация отсутствует. В противном случае, новая параметризация типа функционального интерфейса F<A'1, ..., A'm> строится следующим образом для 1 ≤ i ≤ m:
-
Если B содержит экземплирование (§18.1.3) для αi, T, то A'i = T.
-
В противном случае, A'i = Ai.
Если F<A'1, ..., A'm> не является корректным типом (то есть, аргументы типа находятся вне своих границ), или если F<A'1, ..., A'm> не является подтипом F<A1, ..., Am>, то допустимая параметризация отсутствует. В противном случае, выведенная параметризация — это либо F<A'1, ..., A'm>, если все аргументы типа являются типами, или не-дикий символ параметризации (§9.9) F<A'1, ..., A'm>, если один или несколько аргументов типа по-прежнему являются дикими символами.
Для определения типа функции функционального интерфейса с параметрами диких символов, мы должны «экземплировать» аргументы типа диких символов с конкретными типами. «По умолчанию» подход заключается в простом замене диких символов их границами, как описано в §9.8, но это приводит к ложным ошибкам в тех случаях, когда лямбда-выражение имеет явные типы параметров, которые не соответствуют границам дикого символа. Например:
Predicate<? super Integer> p = (Number n) -> n.equals(23);
Лямбда-выражение является Predicate<Number>, который является подтипом Predicate<? super Integer>, но не Predicate<Integer>. Анализ в этом разделе используется для вывода, что Number является подходящим выбором для аргумента типа Predicate.
Однако, хотя анализ здесь описан с точки зрения общего вывода типов, он намеренно довольно прост. Единственными ограничениями являются ограничения равенства, что означает, что редукция сводится к простому сопоставлению шаблонов. Более мощная стратегия также могла бы выводить ограничения из тела лямбда-выражения. Но, учитывая возможные взаимодействия с выводом для окружающих и/или вложенных вызовов методов с дженериками, это добавит много дополнительной сложности.
При проверке того, что один применимый метод более специфичен, чем другой (§15.12.2.5), где второй метод является обобщённым, необходимо проверить, можно ли вывести некоторое подстановку параметров типа второго метода, чтобы сделать первый метод более специфичным, чем второй.
Пусть m1 — первый метод, а m2 — второй метод. Где у m2 есть параметры типа P1, ..., Pp, пусть α1, ..., αp — переменные вывода, и пусть θ — подстановка [P1:=α1, ..., Pp:=αp].
Пусть e1, ..., ek — выражения аргументов соответствующего вызова. Тогда:
-
Если
m1иm2применимы посредством строгого или свободного вызова (§15.12.2.2, §15.12.2.3), то пусть S1, ..., Sk — типы формальных параметровm1, и пусть T1, ..., Tk — результат применения θ к типам формальных параметровm2. -
Если
m1иm2применимы посредством вызова с переменной арностью (§15.12.2.4), то пусть S1, ..., Sk — первые k типов параметров с переменной арностьюm1, и пусть T1, ..., Tk — результат применения θ к первым k типам параметров с переменной арностьюm2.
Обратите внимание, что к S1, ..., Sk никакая подстановка не применяется; даже если m1 является обобщённым, параметры типа m1 рассматриваются как переменные типов, а не переменные вывода.
Процесс определения, является ли m1 более специфичным, чем m2, следующий:
-
Во-первых, генерируется начальный набор ограничений, B, из объявленных ограничений P1, ..., Pp, как указано в §18.1.3.
-
Во-вторых, для всех i (1 ≤ i ≤ k), генерируется набор формул ограничений или ограничений.
Если Ti — правильный тип, результат — истина, если Si более специфичен, чем Ti для
ei(§15.12.2.5), и ложь в противном случае. (Обратите внимание, что Si всегда является правильным типом.)В противном случае, если Si и Ti не являются одновременно типами функциональных интерфейсов, генерируется формула ограничения ‹Si
<:Ti›.В противном случае, если интерфейс Si является суперинтерфейсом или подинтерфейсом интерфейса Ti (или, где Si или Ti — пересечение типов, какой-либо интерфейс Si является суперинтерфейсом или подинтерфейсом какого-либо интерфейса Ti), генерируется формула ограничения ‹Si
<:Ti›.В противном случае, пусть MTS — тип функции захвата Si, пусть MTS' — тип функции Si (без захвата), и пусть MTT — тип функции Ti. Если MTS и MTT имеют разное количество формальных или параметров типа, или если MTS и MTS' не имеют одинаковых параметров типа (§8.4.4), результат — ложь. В противном случае, следующие формулы ограничений или ограничения генерируются из параметров типа, типов формальных параметров и типов возвращаемых значений MTS и MTT:
-
Пусть A1, ..., An — параметры типа MTS, а B1, ..., Bn — параметры типа MTT.
Пусть θ' — подстановка
[B1:=A1, ..., Bn:=An]. Тогда для всех j (1 ≤ j ≤ n):-
Если ограничение для Aj содержит один из A1, ..., An, а ограничение для Bj не является правильным типом, то false.
-
В противном случае, где X — ограничение для Aj, а Y — ограничение для Bj, ‹X = Y θ'›.
Если ограничение Aj содержит один из A1, ..., An, а ограничение Bj не является правильным типом, то создание ограничения равенства может привести к тому, что переменная вывода будет ограничена переменной типа, которая находится вне области видимости. Так как присвоение значения переменной вывода переменной типа, находящейся вне области видимости, не имеет смысла, мы предпочитаем сразу отказаться от такой ситуации, когда появляется такая возможность. Это упрощение не сохраняет полноту. (Аналогичное замечание относится к обработке типов формальных параметров и типов возвращаемых значений ниже.)
-
-
Пусть U1, ..., Uk — типы формальных параметров MTS, а V1, ..., Vk — типы формальных параметров MTT. Тогда для всех j (1 ≤ j ≤ k):
-
Если Uj содержит один из A1, ..., An, а Vj не является правильным типом, то false.
-
В противном случае, ‹Vj θ'
<:Uj›, и, где U1', ..., Uk' — типы формальных параметров MTS', а A1', ..., An' — параметры типа MTS', ‹Vj[B1:=A1', ..., Bn:=An']= Uj'›
-
-
Пусть RS — тип возвращаемого значения MTS, а RT — тип возвращаемого значения MTT. Тогда:
-
Если RS содержит один из A1, ..., An, а RT не является правильным типом, то false.
-
В противном случае, если
eiявляется выражением лямбда-выражения с явным типом:-
Если RT является
void, то true. -
В противном случае, если RS и RT — типы функциональных интерфейсов, и
eiсодержит по крайней мере одно выражение результата, то для каждого выражения результата вeiэтот весь второй шаг повторяется для вывода ограничений, при которых RS более специфичен, чем RT θ' для данного выражения результата. -
В противном случае, если RS — примитивный тип, а RT — нет, и
eiсодержит по крайней мере одно выражение результата, а каждое выражение результатаei— это автономное выражение (§15.2) примитивного типа, то true. -
В противном случае, если RT — примитивный тип, а RS — нет, и
eiсодержит по крайней мере одно выражение результата, а каждое выражение результатаei— это либо автономное выражение ссылочного типа, либо поливыражение, то true. -
В противном случае, ‹RS
<:RT θ'›.
-
-
В противном случае, если
ei— точное ссылка на метод:-
Если RT является
void, то true. -
В противном случае, если RS — примитивный тип, а RT — нет, и декларация времени компиляции для
eiимеет примитивный тип возвращаемого значения, то true. -
В противном случае, если RT — примитивный тип, а RS — нет, и декларация времени компиляции для
eiимеет ссылочный тип возвращаемого значения, то true. -
В противном случае, ‹RS
<:RT θ'›.
-
-
В противном случае, если
ei— выражение в скобках, эти правила для ограничений, полученных из RS и RT, применяются рекурсивно для содержащегося выражения. -
В противном случае, если
ei— условное выражение, эти правила для ограничений, полученных из RS и RT, применяются рекурсивно для каждого из второго и третьего операндов. -
В противном случае, если
ei—switchвыражение, эти правила для ограничений, полученных из RS и RT, применяются рекурсивно для каждого из его выражений результата. -
В противном случае, false.
-
-
Третье, если
m2применим с помощью вызова с переменным числом аргументов и имеет k+1 параметров, то где Sk+1 — тип k+1-го параметра с переменным числом аргументовm1, а Tk+1 — результат применения θ к типу k+1-го параметра с переменным числом аргументовm2, генерируется ограничение ‹Sk+1<:Tk+1›. -
Четвертое, сгенерированные ограничения и формулы ограничений сводятся и включаются в B, чтобы получить набор ограничений B'.
Если B' не содержит ограничение false, и разрешение всех переменных вывода в B' проходит успешно, то
m1более специфичен, чемm2.В противном случае,
m1не более специфичен, чемm2.
-
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.