Проблема с универсальными типами ArrayList и наследованием интерфейса - PullRequest
7 голосов
/ 24 декабря 2011

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

Я понимаю об использовании интерфейсов вArrayList такой, что если у меня есть:

public interface Weapon { ... }
public class Gun implements Weapon { ...}
public class Knife implements Weapon { ... }

, то вы можете вставить все, что реализует Оружие, в массив оружия:меня смущает понимание того, почему ArrayList<Gun> не совместим с ArrayList<Weapon> другими способами.Для иллюстрации допустимо следующее:

public void interfaceIsTheArgument(Weapon w) { ... }
...
interfaceIsTheArgument(new Gun());
interfaceIsTheArgument(new Knife());

, но это не так:

public void interfaceIsTheArgument(ArrayList<Weapon> w) { ... }
...
interfaceIsTheArgument(new ArrayList<Gun>());
interfaceIsTheArgument(new ArrayList<Knife>());

, поскольку последний вызов функции сообщает, что метод не применим для своих аргументов.

Мой вопрос заключается в том, почему, если метод знает, что он задает ArrayList с интерфейсом в качестве универсального типа, почему не нормально передавать массив ножей в этом последнем выражении?

Ответы [ 2 ]

13 голосов
/ 24 декабря 2011

Чтобы «исправить» код, вам нужно использовать универсальный bound :

public void interfaceIsTheArgument(List<? extends Weapon> w) { ... }
...
interfaceIsTheArgument(new ArrayList<? extends Weapon> ());
interfaceIsTheArgument(new ArrayList<Knife>());

Основная причина: List<Gun> - это , а не подкласс List<Weapon>. Причину этого можно проиллюстрировать следующим кодом:

List<Gun> guns = new ArrayList<Gun>();
// If List<Weapon> was a super type of List<Gun>, this next line would be allowed
List<Weapon> weapons = guns; // Won't compile, but let's assume it did
weapons.add(new Knife());  // Compiles, because Knife is a Weapon
Gun gun = guns.get(0); // Oops! guns.get(0) is a Knife, not a Gun!

Используя границу <? extends Weapon>, мы говорим, что примем любой универсальный тип, который является подклассом из Weapon. Использование границ может быть очень мощным. Этот вид границы является верхним пределом - мы указываем класс верхнего уровня как Weapon.

Существует также нижняя граница , которая использует этот синтаксис:

List<? super Weapon> // accept any type that is a Weapon or higher in the class hierarchy

Итак, когда использовать каждый? Запомните это слово PECS: «Производитель расширяется, потребитель супер». Это означает, что на стороне производителя кода (где объекты созданы ) используется extends, а на стороне потребителя кода (где объекты используются ) us super. Если вы попробуете это несколько раз, вы по опыту поймете, почему это работает хорошо.

Этот ТАК вопрос / ответ хорошо его освещает.

4 голосов
/ 24 декабря 2011

Это один из самых часто задаваемых вопросов о дженериках. Это List<Gun> было List<Weapon>, вы могли бы сделать

List<Gun> gunList = new ArrayList<Gun>();
List<Weapon> weaponList = gunList;
weaponList.add(new Knife());
gunList.get(0).fire(); // ClassCastException

Это, таким образом, нарушит безопасность типов, обещанную генериками.

...