Каковы формальные условия для того, чтобы параметр подстановки в родовом типе Java находился в пределах его границ?

С параметризованными типами в Java, как правила, которые проверяют, находится ли параметр в пределах его привязки, работают именно для подстановочных знаков?

Для класса, подобного этому:

class Foo<T extends Number> {}

Экспериментируя с тем, что принимает компилятор, узнает, что:

  • Подстановочный знак A ? extends с использованием несвязанного типа интерфейса разрешен: Foo<? extends Runnable> действителен
  • Подстановочный знак A ? extends с использованием несвязанного типа класса недопустим: Foo<? extends Thread> недействителен. Это имеет смысл, потому что ни один тип не может быть подтипом как Number, так и Thread
  • В подстановочном знаке ? super нижняя граница в подстановочном знаке должна быть подтипом границы переменной типа: Foo<? super Runnable> не допускается, потому что Runnable не является подтипом Number. Опять же, это ограничение имеет смысл.

Но где эти правила определены? Глядя на Раздел 1.4 языка Java, я не вижу ничего отличительного интерфейса от классов; и при применении моей интерпретации JLS Foo<? super Runnable> считается действительным. Поэтому я, вероятно, что-то неправильно понял. Здесь моя попытка:

В этом разделе JLS:

Параметрированный тип состоит из имени класса или интерфейса C и списка аргументов фактического типа < T1,..., Tn > . Это ошибка времени компиляции, если C не является именем универсального класса или интерфейса, или если количество аргументов типа в списке аргументов фактического типа отличается от числа объявленных параметров типа C. В следующем, всякий раз, когда мы говорим класса или типа интерфейса, мы включаем также общую версию, если явно не исключено. Всюду в этом разделе пусть A1,..., An - формальные параметры типа C, и пусть Bi - объявленная граница Ai. Обозначение [Ai: = Ti] означает замену переменной типа Ai типом Ti, для 1 <= я <= n, и используется в этой спецификации.

Пусть P = G < T1,..., Tn > - параметризованный тип. Должно быть, что после того, как P подвергается преобразованию захвата (§ 5.1.10), что приводит к типу G < X1,..., Xn > для каждого аргумента фактического типа Xi, 1 <= я <= = n, Xi <: Bi [A1: = X1,..., An: = Xn] (п. 4.10), или возникает ошибка времени компиляции.

Примените это к P = Foo<? super Runnable>: это дает C = Foo, n = 1, T1 = ? super Runnable и B1 = Number.

Для преобразования захвата эта часть определения преобразования захвата применяется:

Если Ti является аргументом типа подстановочной формы формы? супер Bi, то Si представляет собой новую переменную типа, верхняя граница которой Ui [A1: = S1,..., An: = Sn] и нижняя граница которой Bi.

Это дает G < X1,..., Xn >= Foo<X>, где X является переменной нового типа с верхней границей Number и нижней границей Runnable. Я не вижу ничего явно запрещающего такую ​​переменную типа.

В B1 = Number нет переменных типа, поэтому Bi [A1: = X1,..., An: = Xn] по-прежнему просто Number. X имеет Number как верхнюю границу (исходящую от преобразования захвата) и согласно правила подтипирования "Прямые супертипы типа переменная - это типы, перечисленные в ее привязке", поэтому X <: Number (= Bi [A1: = X1,..., An: = Xn]), поэтому этот параметр находится в пределах его границ. (Но это не так!)

Следуя тем же рассуждениям, что каждый шаблон находится в пределах его границ, так что что-то здесь не так... Но где именно это рассуждение пошло не так? Как эти правила работают при правильном применении?

Ответ 1

JLS на дженериках является неполным, и вы поймали еще одно отверстие в нем. Нижняя граница переменных типа почти не обсуждается, и я не вижу никаких ограничений в spec на X, имеющих верхнюю границу Number и нижнюю границу Runnable. Вероятно, они оставили это.

Интуитивно, должен быть хотя бы один возможный тип, который удовлетворяет как верхней границе, так и нижней границе переменной типа, иначе переменная и все типы, использующие эту переменную, будут бесполезны. Поскольку это почти наверняка ошибка программирования, компиляция должна завершиться неудачей.

Легко проверить, образуют ли верхний и нижний границы пустой набор типов. Известны все супертипы нижней границы; по крайней мере, одна из них должна быть верхней границей, иначе в обеих границах нет типа.

-

Два случая Foo<? extends A> хорошо определены в спецификации. При преобразовании захвата у нас есть новая переменная типа X с верхней границей A & Number, а spec говорит для верхней границы V1&...&Vm

Это ошибка времени компиляции, если для любых двух классов (а не интерфейсов) Vi и Vj, Vi не является подклассом Vj или наоборот.

Поэтому, если A = Thread, преобразование захвата завершается неудачно.