Недавно я прочитайте эту статью: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html
Мой вопрос, вместо того, чтобы создавать такой метод:
public void drawAll(List extends Shape> shapes){
for (Shape s: shapes) {
s.draw(this);
}
}
Я могу создать такой метод, и все работает нормально:
public void drawAll(List shapes){
for (Shape s: shapes) {
s.draw(this);
}
}
Какой путь мне использовать? Полезен ли подстановочный знак в этом случае?
Это зависит от того, что вам нужно делать. Вам нужно использовать параметр ограниченного типа, если вы хотите сделать что-то вроде этого:
public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
if (shape.isPretty()) {
shapes.add(shape);
}
}
Здесь у нас есть List
и T shape
, поэтому мы можем безопасно shape.add (shape)
. Если был объявлен Список расширяет Shape>
, вы можете НЕ безопасно добавить к нему
(потому что у вас может быть List
и Circle
]).
Таким образом, давая имя параметру ограниченного типа, мы получаем возможность использовать его где-нибудь еще в нашем универсальном методе. Эта информация, конечно, не всегда требуется, поэтому, если вам не нужно много знать о типе (например, ваш drawAll
), тогда достаточно подстановочного знака.
Даже если вы снова не ссылаетесь на параметр ограниченного типа, параметр ограниченного типа по-прежнему требуется, если у вас несколько границ. Вот цитата из FAQs по Java Generics Анжелики Лангер
В чем разница между привязкой подстановочного знака и привязкой параметра типа?
Подстановочный знак может иметь только одну границу, в то время как параметр типа может иметь несколько границ.Подстановочный знак может иметь нижнюю или верхнюю границу, в то время как нижняя граница для параметра типа отсутствует.
Границы подстановочных знаков и границы параметров типа часто путают, потому что они оба называются границами и имеют частично похожий синтаксис. […]
Синтаксис :
привязка параметра типа T расширяет Class & Interface1 &… & InterfaceN подстановочный знак верхняя граница ? расширяет SuperType нижняя граница ? супер подтип
Подстановочный знак может иметь только одну границу: нижнюю или верхнюю. Список границ подстановочных знаков не разрешен.
Параметр типа в качестве ограничения может иметь несколько границ, но не существует такой вещи, как нижняя граница для параметра типа.
Цитаты из Эффективное 2-е издание Java, пункт 28: Используйте ограниченные символы подстановки для повышения гибкости API :
Для максимальной гибкости используйте типы подстановочных знаков во входных параметрах, которые представляют производителей или потребителей. […] PECS означает производитель-
extends
, потребитель-super
[…]Не используйте типы подстановочных знаков в качестве возвращаемых типов . Вместо того, чтобы обеспечивать дополнительную гибкость для ваших пользователей, это заставит их использовать типы подстановочных знаков в клиентском коде. При правильном использовании типы подстановочных знаков почти не видны пользователям класса. Они заставляют методы принимать параметры, которые они должны принять, и отклонять те, которые они должны отклонять. Если пользователь класса должен подумать о типах подстановочных знаков, вероятно, что-то не так с API класса .
Применяя принцип PECS, теперь мы можем вернуться к нашему примеру addIfPretty
и сделать его более гибким, написав следующее:
public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }
Теперь мы можем addIfPretty
, скажем, Обведите
в List
. Очевидно, что это типизированный вариант, но все же наше исходное объявление не было достаточно гибким, чтобы это разрешить.
super T>
означает, и когда его следует использовать, и как эта конструкция должна взаимодействовать с
и extends T>
? В вашем примере вам действительно не нужно использовать T, поскольку вы больше нигде не используете этот тип.
Но если вы сделали что-то вроде:
public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
T s = shapes.get(0);
s.draw(this);
return s;
}
или как сказали polygenlubricants, если вы хотите сопоставить параметр типа в списке с другим параметром типа:
public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
List<T> mergedList = new ArrayList<T>();
mergedList.addAll(shapes1);
mergedList.addAll(shapes2);
for (Shape s: mergedList) {
s.draw(this);
}
}
В первом примере вы получаете немного больше безопасности типа, чем возвращаете просто Shape, поскольку затем вы можете передать результат функции, которая может принимать дочерний объект Shape. Например, вы можете передать List
моему методу, а затем передать полученный Square методу, который принимает только Squares. Если вы использовали '?' вам придется преобразовать полученную форму в Square, что не будет типобезопасным.
Во втором примере вы гарантируете, что оба списка имеют один и тот же параметр типа (что нельзя сделать с «?», Поскольку каждый «?» Отличается), так что вы можете создать список, содержащий все элементы из оба из них.
Второй способ немного более подробный, но он позволяет вам ссылаться на T
внутри него:
for (T shape : shapes) {
...
}
Это единственная разница, насколько я понимаю.