Почему я не могу использовать аргумент типа в параметре типа с несколькими границами? - PullRequest
49 голосов
/ 13 октября 2008

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

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() дает мне ошибку компиляции: «Невозможно указать какой-либо дополнительный связанный адаптер , когда первая граница является параметром типа» (в Eclipse), или «Параметр типа не может сопровождаться другими границами» ( ИДЕЯ), выбирайте.

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

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

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

Ответы [ 4 ]

27 голосов
/ 13 октября 2008

Я также не уверен, почему есть ограничение. Вы можете попробовать отправить дружеское электронное письмо разработчикам Java 5 Generics (главным образом, Гиладу Браче и Нилу Гафтеру).

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

И почему этот случай даже был поддержан? Ответ заключается в том, что множественные границы позволяют управлять стиранием, что позволяет поддерживать двоичную совместимость при обобщении существующих классов. Как объясняется в разделе 17.4 книги Нафталина и Вадлера, метод 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 .

13 голосов
/ 17 октября 2008

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

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

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

    /** 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> и 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>> позволяет вам делать то же самое косвенно, его тоже нет.

11 голосов
/ 13 октября 2008

Вот еще одна цитата из JLS :

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

Что именно это за неловкие ситуации, я не знаю.

2 голосов
/ 13 октября 2008

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

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

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

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

...