Подтвердить что ты не робот

Есть ли разница при указании верхних границ для подстановочных знаков явно?

Предположим, что у меня есть общий class Generic<A extends BaseType>.

Есть ли заметная разница в отношении спецификации языка Java между двумя объявлениями типа?

Generic<?>
Generic<? extends BaseType>

Как насчет вложенных подстановочных знаков?

List<Generic<?>>
List<Generic<? extends BaseType>>

Размышляя об этом, я бы предположил, что это эквивалентно. Generic указывает, что параметр типа A имеет BaseType для верхней границы.

Таким образом, подстановочный знак всегда должен быть "автоматически" или "неявно" ограничен BaseType, независимо от того, я конкретно его укажу или нет.

Ниже я пытаюсь примирить свою интуицию с JLS.


Мне не удалось найти информацию о "неявных" границах, поэтому я начал с рассмотрения правил подтипирования.

Считывая раздел JLS о подтипирование $4.10.2, он говорит:

Учитывая объявление универсального типа C<F1,...,Fn> (n > 0), прямые супертипы параметризованного типа C<T1,...,Tn>, , где Ti (1 ≤ я ≤ n) являются типом, все из следующего:

  • D<U1 θ,...,Uk θ>, где D<U1,...,Uk> - общий тип, являющийся прямым супертипом типового типа C<T1,...,Tn>, а θ - подстановка [F1: = T1,..., Fn: = Tn].

  • C<S1,...,Sn>, где Si содержит Ti (1 ≤ я ≤ n) (п. 4.5.1).

(акцент мой)

Из того, что я понимаю, "подстановочные знаки" не считаются "типами" в JLS. Таким образом, это не может применяться к первым двум, но оно применимо к двум примерам List.

Вместо этого это должно применяться:

Учитывая объявление универсального типа C<F1,...,Fn> (n > 0), прямые супертипы параметризованного типа C<R1,...,Rn> , где хотя бы один из Ri (1 ≤ я ≤ n) является аргументом типа подстановочного символа, являются прямыми супертипами параметризованного типа C<X1,...,Xn>, который является результатом применения преобразования захвата в C<R1,...,Rn> (§5.1.10).

(акцент мой)

Применение конверсия захвата $5.1.10 в Generic<?> и Generic<? extends BaseType>; Я думаю, что я получаю те же границы по новым переменным типа. После преобразования захвата я могу использовать правила "содержит", чтобы установить подтипирование.

В первом примере через

Если Ti является аргументом типа подстановочного типа (п. 4.5.1) формы?, то Si - переменная нового типа, верхняя граница которой Ui [A1: = S1,..., An: = Sn] и чьи нижняя граница - нулевой тип (§4.1).

Так как A 1 is BaseType, свежая переменная имеет верхнюю границу BaseType.

Во втором случае через

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

glb (V1,..., Vm) определяется как V1 и... и Vm.

Я получаю glb(BaseType, BaseType), что опять же BaseType.

Итак, похоже, что отношение подтипирования между Generic<?> и Generic<? extends BaseType> идет в обоих направлениях в соответствии с JLS, которое соответствует моей интуиции.


Для вложенных подстановочных знаков я бы использовал правило "содержит" :

Утверждается, что аргумент типа T1 содержит другой аргумент типа T2, записанный T2 <= T1, если набор типов, обозначаемый T2, является предположительно a подмножество множества типов, обозначаемое T1 под рефлексивным и переходное замыкание следующих правил (где <: обозначает подтипирование (§4.10)):

  • ? extends T <= ? extends S if T <: S

  • ? extends T <= ?

  • ? super T <= ? super S if S <: T

  • ? super T <= ?

  • ? super T <= ? extends Object

  • T <= T

  • T <= ? extends T

  • T <= ? super T

В сочетании с

C<S1,...,Sn>, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).

сверху, я получаю:

List<Generic<?>> является прямым супертипом List<Generic<? extends BaseType>>, если Generic<?> содержит Generic<? extends BaseType>>

Хотя, я не вижу, как я использую правило contains. Единственной дополнительной информацией, которую я могу использовать, является подтипирование в соответствии с правилами. Я уже знаю, что подтипирование происходит в обоих направлениях между двумя типами.

Хотя, если содержит ответ с подтипированием между ними, я мог бы также показать, что List<String> является подтипом List<Object>, которого он не является и не должен быть.

Далее, мне нужно показать что-то из формы Type <= OtherType, и единственным правилом с правой частью формы "type" является T <= T, поэтому эти правила, похоже, вообще не помогают.

Как мне получить, что List<Generic<?>> и List<Generic<? extends BaseType>> являются подтипами друг друга через JLS?

4b9b3361

Ответ 1

Взяв ваш первоначальный вопрос буквально: "Есть ли заметная разница" между Generic<?> и Generic<? extends BaseType>, ответ должен быть, они не эквивалентны.

JLS §4.5.1 четко заявляет:

Подстановочный знак ? extends Object эквивалентен неограниченному шаблону ?.

Таким образом, это будет эквивалентно ? extends BaseType, только если BaseType равно Object, но даже тогда они эквивалентны, но все еще имеют заметные отличия, например. в местах, где конверсия захвата не происходит:

boolean b1 = new Object() instanceof Supplier<?>; // valid code
boolean b2 = new Object() instanceof Supplier<? extends Object>; // invalid

Supplier<?>[] array1; // valid declaration
Supplier<? extends Object>[] array1; // invalid

Возможно, стоит отметить, что в отличие от первой интуиции, учитывая объявление Generic<T extends BaseType>, указание Generic<? extends Object> столь же корректно, как и эквивалентное Generic<?>. Оценки подстановочного знака действительны, если они не являются явно различимыми с параметрами типа, и поскольку границы всегда являются подтипами Object, ? extends Object всегда действителен.

Итак, если у нас есть объявление типа, например

interface NumberSupplier<N extends Number> extends Supplier<N> {}

мы можем написать

NumberSupplier<? extends Object> s1;
NumberSupplier<? extends Serializable> s2;
NumberSupplier<? extends BigInteger> s3;

или даже

NumberSupplier<? extends CharSequence> s4;

Мы можем даже реализовать его без фактического типа, расширяющего Number и CharSequence с помощью () -> null

но не

NumberSupplier<? extends String> s5;

как String и Number доказуемо различны.

Когда дело доходит до присвоений, мы можем использовать уже описанные в вопросе правила подтипирования, чтобы заключить, что NumberSupplier<? extends BigInteger> является подтипом NumberSupplier<? extends Object>, потому что ? extends BigInteger содержит ? extends Object (а также содержит ? extends Number), поскольку BigInteger является подтипом Object и Number, но, как вы правильно отметили, это не относится к параметризованным типам, аргументы типа которых не являются подстановочными знаками.

Итак, если у нас есть такие объявления, как List<NumberSupplier<?>>, List<NumberSupplier<? extends Object>> или List<NumberSupplier<? extends Number>>, и вы хотите узнать, есть ли подтип других в соответствии с § 4.3.5, содержит правило, единственным правилом, которое может применяться, является, когда аргументы типа являются одним и тем же типом (T <= T), но тогда нам не нужны правила подтипирования, так как тогда все эти типы списков являются одинаковыми:

Два ссылочных типа - это один и тот же тип времени компиляции, если они имеют одинаковое двоичное имя (§13.1), и их аргументы типа, если они есть, одинаковы, применяя это определение рекурсивно.

Правило содержит все еще полезное, например. он позволяет заключить, что Map<String,? extends Number> является подтипом Map<String,Integer>, потому что для аргумента первого типа String <= String применяется, а аргументы типа во втором типе параметров покрываются специальным шаблоном, содержащим правило.


Итак, оставшийся вопрос: какое правило позволяет сделать вывод, что NumberSupplier<?>, NumberSupplier<? extends Object> или NumberSupplier<? extends Number> являются одинаковыми типами, так что List<NumberSupplier<?>>, List<NumberSupplier<? extends Object>> или List<NumberSupplier<? extends Number>> можно присваивать друг друга.

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

Учитывая

interface MySupplier<S extends CharSequence&Appendable> extends Supplier<S> {}

Очевидно, что следующие утверждения:

List<MySupplier<? extends CharSequence>> list1 = Collections.emptyList();
List<MySupplier<? extends Appendable>>   list2 = Collections.emptyList();

Поскольку в обоих случаях привязка подстановочных знаков является избыточной, поскольку она соответствует одной из привязанных S s, мы можем догадаться, что они фактически одного и того же типа.

Но javac думает, что они не

list1 = list2; // compiler error
list2 = list1; // dito

хотя любая операция, связанная с преобразованием захвата, завершает совместимые типы, например.

list1.set(0, list2.get(0)); // no problem
list2.set(0, list1.get(0)); // no problem

и выполняет отклоненные задания косвенно:

List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem

но здесь ? не эквивалентно ? extends Object:

List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito

но опять же, косвенные назначения работают.

list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works

Итак, любое правило javac использует здесь, его непереходное, которое исключает отношения подтипа, а также общее правило "его того же типа". Кажется, что это действительно un (der) указано и непосредственно влияет на реализацию. И, как в настоящее время реализовано, ? без границ является особенным, позволяя цепочке присваивания не возможно с любым другим типом подстановочного знака.