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

Почему у вас не может быть нескольких интерфейсов в обобщенном подстановочном знаке?

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

4b9b3361

Ответ 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. Но это сработало для моих нужд.