Интерфейсы и дженерики в 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
является подтипом.