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

Почему я не могу использовать аргумент типа в параметре типа с несколькими ограничениями?

Итак, я понимаю , что следующее не работает, но почему не работает?

interface Adapter<E> {}

class Adaptulator<I> {
    <E, A extends I & Adapter<E>> void add(Class<E> extl, Class<A> intl) {
        addAdapterFactory(new AdapterFactory<E, A>(extl, intl));
    }
}

Метод add() дает мне ошибку компиляции: "Невозможно указать какой-либо дополнительный связанный Adapter <E> , когда первая привязка является параметром типа" (в Eclipse), или "параметр типа не может следовать за другими границами" (в IDEA), сделайте выбор.

Ясно, что вам просто не разрешено использовать параметр типа I там, перед &, и это. (И прежде чем вы спросите, это не сработает, если вы переключите их, потому что нет гарантии, что I не является конкретным классом.) Но почему нет? Я просмотрел FAQ Angelika Langer и не могу найти ответ.

Обычно, когда ограничение каких-то дженериков кажется произвольным, это связано с тем, что вы создали ситуацию, когда система типов не может фактически обеспечить правильность. Но я не вижу, какой случай сломает то, что я пытаюсь сделать здесь. Я бы сказал, может быть, что-то связано с отправкой метода после стирания стилей, но есть только один метод add(), поэтому ему не нравится какая-то двусмысленность...

Может кто-нибудь продемонстрировать эту проблему для меня?

4b9b3361

Ответ 1

Я также не уверен, почему существует ограничение. Вы можете попробовать отправить дружественное электронное письмо разработчикам Java 5 Generics (главным образом Gilad Bracha и Neal Gafter).

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

И почему этот случай даже поддерживался? Ответ заключается в том, что множественные границы позволяют вам управлять стиранием, что позволяет поддерживать двоичную совместимость при создании существующих классов. Как объяснено в разделе 17.4 " book Нафталина и Вадлера, метод max логически имел бы следующую подпись:

public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)

Однако это стирает:

public static Comparable max(Collection coll)

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

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

Тогда стирание его подписи становится:

public static Object max(Collection coll)

который равен сигнатуре max до Generics.

Кажется правдоподобным, что разработчики Java заботились только об этом простом случае и ограничивали другие (более продвинутые) применения типов пересечений, потому что они просто не уверены в сложности, которую он может принести. Поэтому причина такого проектного решения не должна быть возможной проблемой безопасности (как следует из этого вопроса).

Больше обсуждений типов пересечений и ограничений дженериков в предстоящей бумаге OOPSLA.

Ответ 2

Две возможные причины запрета на это:

  • Сложность

    . Sun-ошибка 4899305 предполагает, что привязка, содержащая параметр типа плюс дополнительные параметризованные типы, позволит использовать еще более сложные взаимно-рекурсивные типы, чем уже существует. Короче говоря, ответ Бруно.

  • Возможность указания нелегальных типов. В частности, удваивает общий интерфейс с разными параметрами. Я не могу придумать не надуманный пример, но:

    /** Contains a Comparator<String> that also implements the given type T. */
    class StringComparatorHolder<T, C extends T & Comparator<String>> {
      private final C comparator;
      // ...
    }
     
    void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }

Теперь holder.comparator является Comparator<Integer> и a Comparator<String>. Мне не ясно, сколько проблем это может вызвать для компилятора, но это явно не хорошо. Предположим, в частности, что Comparator имел такой метод:

void sort(List<? extends T> list);

Наш гибрид Comparator<Integer>/Comparator<String> теперь имеет два метода с тем же стиранием:

void sort(List<? extends Integer> list);
void sort(List<? extends String> list);

Это по таким причинам, что вы не можете указать такой тип напрямую:

<T extends Comparator<Integer> & Comparator<String>> void bar() { ... }
java.util.Comparator cannot be inherited with different arguments:
    <java.lang.Integer> and <java.lang.String>

Так как <A extends I & Adapter<E>> позволяет вам делать то же самое косвенно, это тоже.

Ответ 3

Здесь другая цитата из JLS:

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

В чем именно эти неудобные ситуации, я не знаю.

Ответ 4

Это, вероятно, не отвечает на вопрос о корне, но просто хочу указать, что спецификация однозначно запрещает его. Поиск в Google сообщения об ошибке привел меня к в этой записи в блоге, которая далее указывает на jls 4.4:

Сделка состоит либо из переменной типа, либо из класса или типа интерфейса T, за которым последуют дополнительные типы интерфейсов I1,..., In.

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

Почему ограничение? Я понятия не имею.