Глава 18. Вывод типов
Оглавление
Различные анализы на этапе компиляции требуют рассуждений о типах, которые еще неизвестны. К числу таких анализов относятся проверка применимости методов дженериков (§18.5.1) и вывод типа вызова дженериковых методов (§18.5.2). В общем случае, процесс рассуждения о неизвестных типах обозначается как вывод типов.
На высоком уровне вывод типов можно разбить на три процесса:
-
Редукция принимает утверждение о совместимости выражения или типа, называемое формулой ограничения, и сводит его к набору границ на переменные вывода. Часто формула ограничения сводится к другим формулам ограничений, которые необходимо рекурсивно сократить. Используется процедура для определения этих дополнительных формул ограничений и, в конечном итоге, для выражения посредством набора границ условий, при которых выбор для выведенных типов сделает каждую формулу ограничения истинной.
-
Включение поддерживает набор границ переменных вывода, гарантируя, что они согласованы по мере добавления новых границ. Поскольку границы одной переменной могут иногда влиять на возможные варианты выбора другой переменной, этот процесс распространяет границы между такими взаимозависимыми переменными.
-
Разрешение исследует границы переменной вывода и определяет инстанциацию, совместимую с этими границами. Также определяется порядок разрешения взаимозависимых переменных вывода.
Эти процессы тесно связаны: редукция может инициировать включение; включение может привести к дальнейшей редукции; а разрешение может вызвать дальнейшее включение.
-
§18.1 более точно определяет понятия, используемые в качестве промежуточных результатов, и обозначения, используемые для их выражения.
-
§18.2 подробно описывает редукцию.
-
§18.3 подробно описывает включение.
-
§18.4 подробно описывает разрешение.
-
§18.5 определяет, как эти инструменты вывода используются для решения определенных проблем анализа на этапе компиляции.
По сравнению с Java SE 7 Edition Спецификации языка 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 представляют результат преобразования capture (§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 являются заглушками для результатов преобразования capture. Это необходимо, потому что преобразование capture может быть выполнено только над корректным типом, а переменные вывода в 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. (Для выражения создания экземпляра класса соответствующий «метод», используемый для вывода, определен в §15.9.3).
Этот набор ограничений может содержать новые переменные вывода, а также зависимости между этими новыми переменными и переменными вывода в T.
-
Если выражение является условным выражением вида
e1?e2:e3, ограничение сводится к двум формулам ограничений, ‹e2→ T› и ‹e3→ 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).
Формула ограничения вида ‹ЛямбдаВыражение → T›, где T содержит по крайней мере одну переменную вывода, сокращается следующим образом:
-
Если T не является типом функционального интерфейса (§9.8), ограничение сводится к false.
-
В противном случае, пусть T' — это базовый целевой тип, полученный из T, как указано в §15.27.3. Если используется §18.5.3 для вывода параметризованного типа функционального интерфейса, то проверка, что F
<A'1, ..., A'm>является подтипом F<A1, ..., Am>не выполняется (вместо этого это утверждается с формулой ограничения ниже). Пусть целевой тип функции для лямбда-выражения — это тип функции T'. Тогда:-
Если действительный тип функции найти невозможно, ограничение сводится к false.
-
В противном случае, конгруэнтность ЛямбдаВыражение с целевым типом функции утверждается следующим образом:
-
Если количество параметров лямбда-выражения отличается от количества типов параметров типа функции, ограничение сводится к false.
-
Если лямбда-выражение неявно типизировано и один или несколько типов параметров типа функции не являются правильными типами, ограничение сводится к false.
Это условие никогда не возникает на практике из-за обработки неявно типизированных лямбда-выражений в §18.5.1 и подстановки, применённой к целевому типу в §18.5.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) генерирует формулы ограничений на обработку исключений (§18.2.5).
Обратите внимание, что если целевой тип является переменной вывода или если типы параметров целевого типа содержат переменные вывода, мы генерируем false. Во время вывода типа вызова (§18.5.2) выполняются дополнительные подстановки для приведения этих переменных вывода к конкретным значениям, тем самым избегая этой ситуации. (Другими словами, уменьшение на практике никогда не будет «вызываться» с целевым типом одного из этих форм.)
Наконец, обратите внимание, что выражения результата лямбда-выражения должны, согласно §15.27.3, быть совместимыми в контексте присваивания с типом возврата целевого типа, R. Если R является правильным типом, таким как Byte, полученный из Function, то проверка присваиваемости достаточно проста, и уменьшение выполняет это выше. Если R не является правильным типом, таким как α, полученный из <α,Byte>Function, то мы делаем выше упрощающее предположение, что слабая совместимость вызовов будет достаточной. Разница между совместимостью присваивания и слабой совместимостью вызовов заключается в том, что только присваивание допускает сужение константных выражений, таких как <String,α>Byte b = 100;. Следовательно, наше упрощающее предположение не сохраняет полноту: для целевого типа возврата α и выражения результата со целочисленной литеральной константой 100, можно предположить, что α может быть приведено к Byte, но уменьшение фактически не сгенерирует такое ограничение.
Формула ограничения вида ‹Ссылка на метод → T›, где T содержит по крайней мере одну переменную вывода, уменьшается следующим образом:
-
Если T не является типом функционального интерфейса, или если T является типом функционального интерфейса, но не имеет типа функции (§9.9), то ограничение уменьшается до false.
-
В противном случае, если для ссылки на метод нет потенциально применимого метода при нацеливании на T, ограничение уменьшается до false.
-
В противном случае, если ссылка на метод является точной (§15.13.1), то пусть P1, ..., Pn — типы параметров типа функции T, а F1, ..., Fk — типы параметров потенциально применимого метода. Ограничение уменьшается до нового набора ограничений следующим образом:
-
В особом случае, когда n = k+1, параметр типа P1 должен действовать как целевая ссылка вызова. Выражение ссылки на метод обязательно имеет вид ТипСсылки
::[АргументыТипов] Идентификатор. Ограничение уменьшается до ‹P1<:ТипСсылки› и, для всех 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.
-
В противном случае, выполняется поиск объявления во время компиляции, как указано в §15.13.1. Если для ссылки на метод нет объявления во время компиляции, ограничение уменьшается до false. В противном случае существует объявление во время компиляции, и:
-
Если результат типа функции является
void, ограничение уменьшается до true. -
В противном случае, если выражение ссылки на метод опускает АргументыТипов, и объявление во время компиляции является общим методом, и тип возврата объявления во время компиляции содержит по крайней мере один из параметров типа метода, то ограничение уменьшается до набора ограничений B3, который будет использоваться для определения типа вызова ссылки на метод при нацеливании на тип возврата типа функции, как определено в §18.5.2. B3 может содержать новые переменные вывода, а также зависимости между этими новыми переменными и переменными вывода в T.
-
В противном случае, пусть R будет типом возврата типа функции, и пусть R' — результат применения преобразования захвата (§5.1.10) к типу возврата типа вызова (§15.12.2.6) объявления во время компиляции. Если R' является
void, ограничение уменьшается до false; в противном случае ограничение уменьшается до ‹R' → R›.
-
-
Стратегия определения типа возврата для общего метода, на который есть ссылка, следуют той же схеме, что и для общих вызовов методов (§18.2.1). Это может включать «поднятие» границ в внешний контекст и вывод зависимостей между двумя наборами переменных вывода.
Формула ограничения вида ‹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.
Аналогично, обращение с преобразованием без проверки приводит к потере полноты в случаях, когда T не является параметризованным типом (например, если T является переменной вывода). В таких ситуациях обычно неясно, нужно ли это преобразование без проверки. Поскольку преобразования без проверки вводят предупреждения о не проверенных преобразованиях, вывод предпочитает избегать их, если это не явно необходимо.
Формула ограничения вида ‹S <: T› сокращается следующим образом:
-
Если S и T — это правильные типы, ограничение сводится к истинно, если S является подтипом T (§4.10), и к ложно в противном случае.
-
В противном случае, если S — это нулевой тип, ограничение сводится к истинно.
-
В противном случае, если T — это нулевой тип, ограничение сводится к ложно.
-
В противном случае, если 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 = 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'›. -
В противном случае, ограничение сокращается до 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.
Формула ограничения вида ‹Выражение лямбда →throws T› сокращается следующим образом:
-
Если T не является типом функционального интерфейса (§9.8), ограничение сокращается до false.
-
В противном случае, пусть целевой тип функции для выражения лямбда определяется, как указано в §15.27.3. Если не удается найти допустимый тип функции, ограничение сокращается до false.
-
В противном случае, если выражение лямбда имеет неявный тип, и один или несколько типов параметров типа функции не являются допустимыми типами, ограничение сокращается до false.
Это условие никогда не возникает на практике из-за подстановки, применяемой к целевому типу в §18.5.2.
-
В противном случае, если тип возвращаемого значения типа функции не является
voidи не является допустимым типом, ограничение сокращается до false.Это условие никогда не возникает на практике из-за подстановки, применяемой к целевому типу в §18.5.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 — не более чемException,ThrowableиObject, тогда 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 включает для всех i (1 ≤ i ≤ k), где
eiактуально для применимости, ‹ei→ Fi θ›. -
Для проверки применимости по свободному вызову:
Если k ≠ n, метод неприменим, и нет необходимости продолжать вывод.
В противном случае, C включает для всех i (1 ≤ i ≤ k), где
eiактуально для применимости, ‹ei→ Fi θ›. -
Для проверки применимости по вызову с переменной арностью:
Пусть F'1, ..., F'k — первые k типы параметров с переменной арностью
m(§15.12.2.4). C включает для всех i (1 ≤ i ≤ k), гдеeiактуально для применимости, ‹ei→ F'i θ›.
-
-
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) выбранного метода выполняется следующим образом:
-
Пусть θ будет подстановкой
[P1:=α1, ..., Pp:=αp], определенной в §18.5.1 для замены параметров типаmна переменные вывода. -
Пусть B2 — множество ограничений, полученное в результате редукции, чтобы продемонстрировать, что
mприменим в §18.5.1. (Хотя в §18.5.1 необходимо было продемонстрировать, что переменные вывода в B2 могут быть разрешены, для установления применимости, подстановки, полученные в результате этого шага разрешения, не считаются частью B2.) -
Если вызов не является поливыражением, пусть множество ограничений B3 будет таким же, как B2.
Если вызов является поливыражением, пусть множество ограничений B3 выводится из B2 следующим образом. Пусть R — тип возвращаемого значения
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.
-
-
Множество формул ограничений C строится следующим образом.
Пусть
e1, ...,ek— фактические выражения аргументов вызова. Еслиmприменима по строгому или свободному вызову, пусть F1, ..., Fk — типы формальных параметровm; еслиmприменима по вызову с переменным числом аргументов, пусть F1, ..., Fk — первые k типов параметров с переменным числом аргументовm(§15.12.2.4). Тогда:-
Для всех i (1 ≤ i ≤ k), если
eiне относится к применимости, C содержит ‹ei→ Fi θ›. -
Для всех i (1 ≤ i ≤ k), могут быть включены дополнительные ограничения, в зависимости от формы
ei:-
Если
ei— LambdaExpression, C содержит ‹LambdaExpression →throws Fi θ›.Кроме того, тело лямбды проверяется на наличие дополнительных ограничений:
-
Для тела лямбды в виде блока, поиск применяется рекурсивно к каждому выражению результата.
-
Для выражения создания экземпляра класса (§15.9) или выражения вызова метода (§15.12), C содержит все формулы ограничений, которые появились бы в множестве C, сгенерированном по §18.5.2, при выводе типа вызова поливыражения.
-
Для выражения в скобках поиск применяется рекурсивно к вложенному выражению.
-
Для условного выражения поиск применяется рекурсивно ко второму и третьему операндам.
-
Для лямбда-выражения поиск применяется рекурсивно к телу лямбды.
-
-
Если
ei— MethodReference, C содержит ‹MethodReference →throws Fi θ›. -
Если
ei— выражение создания экземпляра поликласса (§15.9) или поливызов метода (§15.12), C содержит все формулы ограничений, которые появились бы в множестве C, сгенерированном по §18.5.2, при выводе типа вызова поливыражения. -
Если
ei— выражение в скобках, эти правила применяются рекурсивно к вложенному выражению. -
Если
ei— условное выражение, эти правила применяются рекурсивно ко второму и третьему операндам.
-
-
-
Пока C не пусто, повторяется следующий процесс, начиная с набора связанных значений B3 и накапливая новые значения в «текущий» набор связанных значений, в конечном итоге получая новый набор связанных значений B4:
-
Выбирается подмножество ограничений из C, удовлетворяющее свойству, что для каждого ограничения ни одна входная переменная не может влиять на выходную переменную другого ограничения в C. Термины «входная переменная» и «выходная переменная» определены ниже. Переменная вывода α может влиять на переменную вывода β, если α зависит от разрешения β (§18.4), или наоборот; или если существует третья переменная вывода γ, такая что α может влиять на γ, а γ может влиять на β.
Если это подмножество пусто, то существует цикл (или циклы) в графе зависимостей между ограничениями. В этом случае все ограничения, участвующие в цикле (или циклах) зависимостей и не зависящие от каких-либо ограничений вне цикла (или циклов), рассматриваются. Выбирается одно ограничение из рассмотренных, следующим образом:
-
Если какое-либо из рассмотренных ограничений имеет вид ‹Выражение → 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 или если разрешение не удалось, возникает ошибка времени компиляции.
-
Вывод типа вызова может потребовать тщательной последовательности сокращения формул ограничений вида ‹Выражение → 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›. -
Для всех других формул ограничений входных переменных нет.
Выходные переменные этих ограничений — все переменные вывода, указанные в типе справа от ограничения, T, которые не являются входными переменными.
Важно отметить, что при поиске типа вызова метода участвуют два «раунда» вывода типа. Это необходимо, чтобы разрешить влиянию целевого типа на тип вызова без влияния на выбор подходящего метода. Первый раунд создает связанный набор и проверяет, существует ли решение, но не принимает его. Второй раунд сокращает дополнительные ограничения, затем выполняет второе решение, на этот раз «по-настоящему».
Рассмотрим пример из предыдущего раздела:
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 SE 7 Edition Спецификации языка Java®.
Если лямбда-выражение со явными типами параметров P1, ..., Pn имеет цель в виде типа функционального интерфейса F<A1, ..., Am>, содержащего хотя бы один аргумент с подстановочным типом, то параметризация F может быть выведена как основной целевой тип лямбда-выражения следующим образом.
Пусть Q1, ..., Qk — типы параметров типа функции F<α1, ..., αm>, где α1, ..., αm — новые переменные вывода.
Если n ≠ k, то допустимая параметризация не существует. В противном случае формируется множество формул ограничений с, для всех i (1 ≤ i ≤ n), ‹Pi = Qi›. Это множество формул ограничений сводится к формированию множества ограничений 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 — это правильный тип, результат равен true, если Si более специфичен, чем Ti, для
ei(§15.12.2.5), и false в противном случае. (Обратите внимание, что Si всегда является правильным типом.)В противном случае, если Ti не является типом функционального интерфейса, генерируется формула ограничения ‹Si
<:Ti›.В противном случае, Ti является параметризацией функционального интерфейса I. Необходимо определить, удовлетворяет ли Si следующим пяти условиям:
-
Si является типом функционального интерфейса.
-
Si не является суперинтерфейсом I и не является параметризацией суперинтерфейса I.
-
Si не является подинтерфейсом I и не является параметризацией подинтерфейса I.
-
Если Si является пересекающимся типом, по крайней мере один элемент пересечения не является суперинтерфейсом I и не является параметризацией суперинтерфейса I.
-
Если Si является пересекающимся типом, ни один элемент пересечения не является подинтерфейсом I и не является параметризацией подинтерфейса I.
Если все пять условий выполняются, генерируются следующие формулы ограничений или ограничения (где U1 ... Uk и R1 — типы параметров и тип возвращаемого значения типа функции захвата Si, а V1 ... Vk и R2 — типы параметров и тип возвращаемого значения типа функции Ti):
-
Если
ei— это лямбда-выражение с явно указанным типом:-
Для всех j (1 ≤ j ≤ k), ‹Uj = Vj›.
-
Если R2 является
void, true. -
В противном случае, если R1 и R2 — типы функциональных интерфейсов, и ни один интерфейс не является подинтерфейсом другого, и
eiимеет по крайней мере одно выражение результата, то эти правила применяются рекурсивно к R1 и R2 для каждого выражения результата вei. -
В противном случае, если R1 является примитивным типом, а R2 — нет, и
eiимеет по крайней мере одно выражение результата, и каждое выражение результатаeiявляется автономным выражением (§15.2) примитивного типа, true. -
В противном случае, если R2 является примитивным типом, а R1 — нет, и
eiимеет по крайней мере одно выражение результата, и каждое выражение результатаeiявляется автономным выражением ссылочного типа или поливыражением, true. -
В противном случае, ‹R1
<:R2›.
-
-
Если
ei— это точное ссылка на метод:-
Для всех j (1 ≤ j ≤ k), ‹Uj = Vj›.
-
Если R2 —
void, true. -
В противном случае, если R1 является примитивным типом, а R2 — нет, и объявление на этапе компиляции для
eiимеет примитивный тип возвращаемого значения, true. -
В противном случае, если R2 является примитивным типом, а R1 — нет, и объявление на этапе компиляции для
eiимеет ссылочный тип возвращаемого значения, true. -
В противном случае, ‹R1
<:R2›.
-
-
Если
ei— это выражение в скобках, эти правила применяются рекурсивно к содержащемуся выражению. -
Если
ei— это условное выражение, эти правила применяются рекурсивно к каждому из второго и третьего операндов. -
В противном случае, false.
Если пять ограничений на Si не выполнены, вместо этого генерируется формула ограничения ‹Si
<:Ti›. -
-
В-третьих, если
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.