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

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

Учитывая такой класс:

class Foo<T extends Number> {}

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

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

Но где определены эти правила? Глядя на раздел 4.5 Java Language Specification, Я не вижу ничего, что бы отличало интерфейсы от классов; и при применении моей интерпретации JLS Foo<? super Runnable> считается действительным. Так что, наверное, я что-то не так понял. Вот моя попытка:

Из этого раздела JLS:

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

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

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

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

Если Ti является аргументом подстановочного знака формы ? super 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 ]), поэтому этот параметр находится в допустимых пределах. (Но это не так!)

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


person Wouter Coekaerts    schedule 09.08.2011    source источник


Ответы (1)


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

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

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

--

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

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

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

person irreputable    schedule 09.08.2011