Почему у вас не может быть нескольких интерфейсов в обобщенном подстановочном знаке?
Я знаю всевозможные контр-интуитивные свойства общих типов Java. Здесь, в частности, я не понимаю, и я надеюсь, что кто-то сможет мне объяснить. При указании параметра типа для класса или интерфейса вы можете связать его так, чтобы он реализовал несколько интерфейсов с помощью public class Foo<T extends InterfaceA & InterfaceB>
. Однако, если вы создаете экземпляр фактического объекта, это больше не работает. List<? extends InterfaceA>
отлично, но List<? extends InterfaceA & InterfaceB>
не удается скомпилировать. Рассмотрим следующий полный фрагмент:
import java.util.List;
public class Test {
static interface A {
public int getSomething();
}
static interface B {
public int getSomethingElse();
}
static class AandB implements A, B {
public int getSomething() { return 1; }
public int getSomethingElse() { return 2; }
}
// Notice the multiple bounds here. This works.
static class AandBList<T extends A & B> {
List<T> list;
public List<T> getList() { return list; }
}
public static void main(String [] args) {
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
// This last one fails to compile!
List<? extends A & B> foobar = new LinkedList<AandB>();
}
}
Кажется, семантика bar
должна быть четко определена - я не могу думать о какой-либо потере безопасности типа, разрешая пересечение двух типов, а не только одного. Я уверен, что есть объяснение. Кто-нибудь знает, что это такое?
Ответы
Ответ 1
Интересно, что интерфейс java.lang.reflect.WildcardType
выглядит так, что он поддерживает как верхние границы, так и нижние границы для аргумента arg; и каждый может содержать несколько границ
Type[] getUpperBounds();
Type[] getLowerBounds();
Это намного больше, чем позволяет язык. Там скрытый комментарий в исходном коде
// one or many? Up to language spec; currently only one, but this API
// allows for generalization.
Автор интерфейса, похоже, считает, что это случайное ограничение.
Консервированный ответ на ваш вопрос: дженерики уже слишком сложны, как есть; добавление дополнительной сложности может оказаться последней каплей.
Чтобы подстановочный знак имел несколько верхних границ, нужно просканировать спецификацию и убедиться, что вся система все еще работает.
Одна проблема, которую я знаю, будет в выводе типа. Текущие правила вывода просто не могут иметь дело с типами перехвата. Нет правила уменьшить ограничение A&B << C
. Если бы мы уменьшили его до
A<<C
or
A<<B
любой текущий движок вывода должен пройти капитальный ремонт, чтобы позволить такую бифуркацию. Но настоящая серьезная проблема заключается в том, что это позволяет использовать несколько решений, но нет оснований предпочитать друг друга.
Однако для вывода безопасности не требуется вывод; мы можем просто отказаться от вывода в этом случае и попросить программиста явно заполнить аргументы типа. Поэтому трудность в выводе не является сильным аргументом против типов перехвата.
Ответ 2
Из Спецификация языка Java:
4.9 Типы пересечений Тип пересечения принимает вид T1 и... и Tn, n > 0, где Ti, 1in, являются выражениями типа. Типы пересечений возникают в процессах конверсии захвата (§ 5.1.10) и вывода типа (§15.12.2.7). Невозможно написать тип пересечения непосредственно как часть программы; синтаксис не поддерживает этот. Значения типа пересечения - это те объекты, которые являются значениями всех типов Ti, для 1in.
Так почему же это не поддерживается? Я предполагаю, что вы должны делать с такой штукой? - предположим, что это возможно:
List<? extends A & B> list = ...
Тогда что должно
list.get(0);
вернуться? Нет синтаксиса для получения возвращаемого значения A & B
. Добавление чего-то в такой список было бы невозможным, так что это в основном бесполезно.
Ответ 3
Нет проблем... просто объявите тип, который вам нужен в сигнатуре метода.
Это компилируется:
public static <T extends A & B> void main(String[] args) throws Exception
{
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
List<T> foobar = new LinkedList<T>(); // This compiles!
}
Ответ 4
Хороший вопрос. Мне потребовалось некоторое время, чтобы понять.
Давайте упростим ваш случай: вы пытаетесь сделать то же самое, как если бы вы объявили класс, который расширяет 2 интерфейса, а затем переменную, которая имеет тип 2 интерфейса, что-то вроде этого:
class MyClass implements Int1, Int2 { }
Int1 & Int2 variable = new MyClass()
Конечно, незаконно.
И это эквивалентно тому, что вы пытаетесь сделать с дженериками.
То, что вы пытаетесь сделать, это:
List<? extends A & B> foobar;
Но тогда, чтобы использовать foobar, , вам нужно будет использовать переменную обоих интерфейсов следующим образом:
A & B element = foobar.get(0);
Что означает не правовой в Java. Это означает, что вы объявляете элементы списка как двух типов одновременно, и даже если наши мозги могут справиться с этим, язык Java не может.
Ответ 5
Для чего это стоит: если кому-то интересно это, потому что они действительно хотели бы использовать это на практике, я работал над этим, определяя интерфейс, который содержит объединение всех методов во всех интерфейсах и классе, которые я работать с. то есть я пытался сделать следующее:
class A {}
interface B {}
List<? extends A & B> list;
что является незаконным - поэтому вместо этого я сделал это:
class A {
<A methods>
}
interface B {
<B methods>
}
interface C {
<A methods>
<B methods>
}
List<C> list;
Это все еще не так полезно, как возможность ввести что-то как List<? extends A implements B>
, например. если кто-то добавляет или удаляет методы в или B, ввод списка не будет обновляться автоматически, для этого требуется ручное изменение C. Но это сработало для моих нужд.