Интерфейсы и дженерики в Java

У меня есть код:

Set<? extends Notifiable> notifiables;

Уведомляемый - это интерфейс. Я не понимаю разницу между приведенным выше кодом и:

Set<Notifiable> notifiables;

Если Notifiable был классом, тогда я бы понял разницу, первый код разрешил Notifyiable и любой подкласс Notifiable, тогда как второй код разрешил Notifyable (а не какие-либо подклассы)

Как вы не можете иметь экземпляр интерфейса, что я могу добавить /etc в набор? Мне кажется, что есть только два варианта: либо что-либо, что реализует Notifyiable (в этом случае, как это отличается от первого кода), либо только "экземпляры Notifyable", которые не могут существовать, и поэтому ничего (что бессмысленно и должен вызывать ошибку времени компиляции).

Ответы

Ответ 1

A Set<Notifiable> может содержать экземпляры классов, которые реализуют Notifiable. Он не ограничивается только экземплярами, чей конкретный тип Notifiable (и вы правы, нет такой вещи). Но Set<Notifiable> гарантирует, что он может содержать любой тип Notifiable, поскольку он имеет метод add(Notifiable), который может принимать все, что реализует интерфейс.

Предположим, что у вас есть классы с именем Foo и Bar, которые оба реализуют Notifiable. Если вы создаете Set<Foo> - то есть набор, в который разрешено содержать только экземпляры Foo и его подтипы, вы не можете передать его методу, который принимает Set<Notifiable>, потому что этот метод может добавить вещи, которые не являются Foo экземплярами, например Bar.

public void addABar(final Set<Notifiable> notifiables) {
    notifiables.add(new Bar());  // OK, since Bar is a subtype of Notifiable
}

public void wontWork() {
  final Set<Foo> foos = new HashSet<>();
  addABar(foos);  // Compile error, can't convert Set<Foo> to Set<Notifiable>
}

Но иногда вы хотите написать метод, который может принимать такие вещи, как Set<Foo> и Set<Bar> в дополнение к Set<Notifiable>. То, в которое входит подстановочный знак. A Set<? extends Notifiable> гарантирует, что все в нем является чем-то вроде Notifiable, но это не гарантирует, что к нему можно добавить любой тип Notifiable; это позволило ограничить подтип. Вы не можете называть add() на нем, потому что этот метод теперь add(? extends Notifiable) вместо add(Notifiable), и вы не можете вызвать метод, тип аргумента которого неизвестен.

Обычно вы используете это, когда вам не нужно добавлять элементы, но вам нужно посмотреть на существующие элементы и вызвать методы интерфейса Notifiable на них, и вы хотите разрешить вызывающему абоненту передавать наборы подтипов, таких как Set<Foo>.

Например:

public void notifyAll(final Set<? extends Notifiable> notifiables) {
    for (final Notifiable notifiable : notifiables) {
        notifiable.notify();
    }
}

public void example() {
    final Set<Foo> foos = whatever();
    notifyAll(foos);  // OK, since a Set<Foo> is a Set<? extends Notifiable>
}

Если notifyAll() взял Set<Notifiable>, вы не смогли бы передать ему foos.

Ответ 2

Используйте более простой пример:

Set<? extends Serializable> serializables;

Этот объявляет переменную, которая может содержать ссылку на Set из Integer s, Float и так далее:

 serializables = new HashSet<Serializable>(); // valid
 serializables = new HashSet<Number>(); // this is valid as well
 serializables = new HashSet<Integer>(); // valid

Это, с другой стороны:

Set<Serializable> serializables;

может содержать только Set объектов Serializable:

serializables = new HashSet<Serializable>();
serializables = new TreeSet<Serializable>();

поэтому эта ошибка будет компилятором:

List<Serializable> numbers = new ArrayList<Integer>();

Следствие:

Если вам нужно поле, которое может содержать любой подтип Notifiable, используйте:

Set<Notifiable> notifiables = new HashSet<Notifiable>();

если вы хотите ограничить подтип Notifiable, который можно использовать, тогда это способ:

Set<? extends Notifiable> notifiables = new HashSet<MyNotifiable>();

Добавление:

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

Set<? extends Notifiable> notifiables = new HashSet<NotifiableA>();
notifiables  = new HashSet<NotifiableB>(); 

Ответ 3

Для одноуровневых генериков в типах коллекций роль подстановочного символа ? extends не очень значительна, потому что все методы Collection выглядят как

E get(int index);
boolean add(E e);

которые фактически являются такими же:

? extends E get(int index);
boolean add(? extends E e);

из-за правил полиморфизма подтипа Java.

Однако подстановочный знак значим, когда действуют правила подтипа:

    List<Integer> list = Arrays.asList(1);
    List<? extends Number> numSubTypeList = list; // Works
    List<Number> numList = list; // Illegal

Аналогично для многоуровневых дженериков:

    Collection<Collection<Number>> col1 = new ArrayList<Collection<Number>>();
    col1.add(list); // Illegal

    Collection<Collection<? extends Number>> col2 = new ArrayList<Collection<? extends Number>>();
    col2.add(list); // Works

Однако даже подпись col2 не подходит для общих библиотек. Вероятно, они ожидают Collection<? extends Collection<? extends Number>>, который col2 является подтипом.